1# Copyright 2013 IBM Corp. 2# Copyright (c) 2013 OpenStack Foundation 3# All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# not use this file except in compliance with the License. You may obtain 7# a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations 15# under the License. 16 17 18import copy 19 20import mock 21 22from cinder import context 23from cinder import exception 24from cinder.i18n import _ 25from cinder.objects import fields 26from cinder import test 27from cinder.volume import configuration as conf 28from cinder.volume.drivers.ibm.ibm_storage import ibm_storage 29from cinder.volume import volume_types 30 31FAKE = "fake" 32FAKE2 = "fake2" 33CANNOT_DELETE = "Can not delete" 34TOO_BIG_VOLUME_SIZE = 12000 35POOL_SIZE = 100 36GROUP_ID = 1 37VOLUME = {'size': 16, 38 'name': FAKE, 39 'id': 1, 40 'status': 'available'} 41VOLUME2 = {'size': 32, 42 'name': FAKE2, 43 'id': 2, 44 'status': 'available'} 45GROUP_VOLUME = {'size': 16, 46 'name': FAKE, 47 'id': 3, 48 'group_id': GROUP_ID, 49 'status': 'available'} 50 51MANAGED_FAKE = "managed_fake" 52MANAGED_VOLUME = {'size': 16, 53 'name': MANAGED_FAKE, 54 'id': 2} 55 56REPLICA_FAKE = "repicated_fake" 57REPLICATED_VOLUME = {'size': 64, 58 'name': REPLICA_FAKE, 59 'id': '2', 60 'replication_status': fields.ReplicationStatus.ENABLED} 61 62REPLICATED_VOLUME_DISABLED = REPLICATED_VOLUME.copy() 63REPLICATED_VOLUME_DISABLED['replication_status'] = ( 64 fields.ReplicationStatus.DISABLED) 65 66REPLICATION_TARGETS = [{'target_device_id': 'fakedevice'}] 67SECONDARY = 'fakedevice' 68FAKE_FAILOVER_HOST = 'fakehost@fakebackend#fakepool' 69FAKE_PROVIDER_LOCATION = 'fake_provider_location' 70FAKE_DRIVER_DATA = 'fake_driver_data' 71 72CONTEXT = {} 73 74FAKESNAPSHOT = 'fakesnapshot' 75SNAPSHOT = {'name': 'fakesnapshot', 76 'id': 3} 77 78GROUP = {'id': GROUP_ID, } 79GROUP_SNAPSHOT_ID = 1 80GROUP_SNAPSHOT = {'id': GROUP_SNAPSHOT_ID, 81 'group_id': GROUP_ID} 82 83CONNECTOR = {'initiator': "iqn.2012-07.org.fake:01:948f189c4695", } 84 85FAKE_PROXY = 'cinder.tests.unit.volume.drivers.ibm.test_ibm_storage' \ 86 '.IBMStorageFakeProxyDriver' 87 88 89class IBMStorageFakeProxyDriver(object): 90 """Fake IBM Storage driver 91 92 Fake IBM Storage driver for IBM XIV, Spectrum Accelerate, 93 FlashSystem A9000, FlashSystem A9000R and DS8000 storage systems. 94 """ 95 96 def __init__(self, ibm_storage_info, logger, expt, 97 driver=None, active_backend_id=None, host=None): 98 """Initialize Proxy.""" 99 100 self.ibm_storage_info = ibm_storage_info 101 self.logger = logger 102 self.exception = expt 103 self.storage_portal = \ 104 self.storage_iqn = FAKE 105 106 self.volumes = {} 107 self.snapshots = {} 108 self.driver = driver 109 110 def setup(self, context): 111 if self.ibm_storage_info['user'] != self.driver\ 112 .configuration.san_login: 113 raise self.exception.NotAuthorized() 114 115 if self.ibm_storage_info['address'] != self.driver\ 116 .configuration.san_ip: 117 raise self.exception.HostNotFound(host='fake') 118 119 def create_volume(self, volume): 120 if volume['size'] > POOL_SIZE: 121 raise self.exception.VolumeBackendAPIException(data='blah') 122 self.volumes[volume['name']] = volume 123 124 def volume_exists(self, volume): 125 return self.volumes.get(volume['name'], None) is not None 126 127 def delete_volume(self, volume): 128 if self.volumes.get(volume['name'], None) is not None: 129 del self.volumes[volume['name']] 130 131 def manage_volume_get_size(self, volume, existing_ref): 132 if self.volumes.get(existing_ref['source-name'], None) is None: 133 raise self.exception.VolumeNotFound(volume_id=volume['id']) 134 return self.volumes[existing_ref['source-name']]['size'] 135 136 def manage_volume(self, volume, existing_ref): 137 if self.volumes.get(existing_ref['source-name'], None) is None: 138 raise self.exception.VolumeNotFound(volume_id=volume['id']) 139 volume['size'] = MANAGED_VOLUME['size'] 140 return {} 141 142 def unmanage_volume(self, volume): 143 pass 144 145 def initialize_connection(self, volume, connector): 146 if not self.volume_exists(volume): 147 raise self.exception.VolumeNotFound(volume_id=volume['id']) 148 lun_id = volume['id'] 149 150 self.volumes[volume['name']]['attached'] = connector 151 152 return {'driver_volume_type': 'iscsi', 153 'data': {'target_discovered': True, 154 'target_portal': self.storage_portal, 155 'target_iqn': self.storage_iqn, 156 'target_lun': lun_id, 157 'volume_id': volume['id'], 158 'multipath': True, 159 'provider_location': "%s,1 %s %s" % ( 160 self.storage_portal, 161 self.storage_iqn, 162 lun_id), }, 163 } 164 165 def terminate_connection(self, volume, connector): 166 if not self.volume_exists(volume): 167 raise self.exception.VolumeNotFound(volume_id=volume['id']) 168 if not self.is_volume_attached(volume, connector): 169 raise self.exception.NotFound(_('Volume not found for ' 170 'instance %(instance_id)s.') 171 % {'instance_id': 'fake'}) 172 del self.volumes[volume['name']]['attached'] 173 174 def is_volume_attached(self, volume, connector): 175 if not self.volume_exists(volume): 176 raise self.exception.VolumeNotFound(volume_id=volume['id']) 177 178 return (self.volumes[volume['name']].get('attached', None) 179 == connector) 180 181 def get_replication_status(self, context, volume): 182 if volume['replication_status'] == 'invalid_status_val': 183 raise exception.CinderException() 184 return {'replication_status': 'active'} 185 186 def retype(self, ctxt, volume, new_type, diff, host): 187 volume['easytier'] = new_type['extra_specs']['easytier'] 188 return True, volume 189 190 def create_group(self, ctxt, group): 191 192 volumes = [volume for k, volume in self.volumes.items() 193 if volume['group_id'] == group['id']] 194 195 if volumes: 196 raise exception.CinderException( 197 message='The group id of volume may be wrong.') 198 199 return {'status': fields.GroupStatus.AVAILABLE} 200 201 def delete_group(self, ctxt, group, volumes): 202 for volume in self.volumes.values(): 203 if group.get('id') == volume.get('group_id'): 204 if volume['name'] == CANNOT_DELETE: 205 raise exception.VolumeBackendAPIException( 206 message='Volume can not be deleted') 207 else: 208 volume['status'] = 'deleted' 209 volumes.append(volume) 210 211 # Delete snapshots in group 212 self.snapshots = {k: snap for k, snap in self.snapshots.items() 213 if not(snap.get('group_id') == group.get('id'))} 214 215 # Delete volume in group 216 self.volumes = {k: vol for k, vol in self.volumes.items() 217 if not(vol.get('group_id') == group.get('id'))} 218 219 return {'status': fields.GroupStatus.DELETED}, volumes 220 221 def update_group(self, context, group, add_volumes, remove_volumes): 222 model_update = {'status': fields.GroupStatus.AVAILABLE} 223 return model_update, None, None 224 225 def create_group_from_src(self, context, group, volumes, group_snapshot, 226 snapshots, source_group=None, source_vols=None): 227 228 return None, None 229 230 def create_group_snapshot(self, ctxt, group_snapshot, snapshots): 231 for volume in self.volumes.values(): 232 if group_snapshot.get('group_id') == volume.get('group_id'): 233 if volume['size'] > POOL_SIZE / 2: 234 raise self.exception.VolumeBackendAPIException(data='blah') 235 236 snapshot = copy.deepcopy(volume) 237 snapshot['name'] = ( 238 CANNOT_DELETE if snapshot['name'] == CANNOT_DELETE 239 else snapshot['name'] + 'Snapshot') 240 snapshot['status'] = 'available' 241 snapshot['group_snapshot_id'] = group_snapshot.get('id') 242 snapshot['group_id'] = group_snapshot.get('group_id') 243 self.snapshots[snapshot['name']] = snapshot 244 snapshots.append(snapshot) 245 246 return {'status': fields.GroupSnapshotStatus.AVAILABLE}, snapshots 247 248 def delete_group_snapshot(self, ctxt, group_snapshot, snapshots): 249 updated_snapshots = [] 250 for snapshot in snapshots: 251 if snapshot['name'] == CANNOT_DELETE: 252 raise exception.VolumeBackendAPIException( 253 message='Snapshot can not be deleted') 254 else: 255 snapshot['status'] = 'deleted' 256 updated_snapshots.append(snapshot) 257 258 # Delete snapshots in group 259 self.snapshots = {k: snap for k, snap in self.snapshots.items() 260 if not(snap.get('group_id') 261 == group_snapshot.get('group_snapshot_id'))} 262 263 return {'status': 'deleted'}, updated_snapshots 264 265 def freeze_backend(self, context): 266 return True 267 268 def thaw_backend(self, context): 269 return True 270 271 def failover_host(self, context, volumes, secondary_id, groups=None): 272 target_id = 'BLA' 273 volume_update_list = [] 274 for volume in volumes: 275 status = 'failed-over' 276 if volume['replication_status'] == 'invalid_status_val': 277 status = 'error' 278 volume_update_list.append( 279 {'volume_id': volume['id'], 280 'updates': {'replication_status': status}}) 281 282 return target_id, volume_update_list, [] 283 284 def enable_replication(self, context, group, volumes): 285 vol_status = [] 286 for vol in volumes: 287 vol_status.append( 288 {'id': vol['id'], 289 'replication_status': fields.ReplicationStatus.ENABLED}) 290 return ( 291 {'replication_status': fields.ReplicationStatus.ENABLED}, 292 vol_status) 293 294 def disable_replication(self, context, group, volumes): 295 volume_update_list = [] 296 for volume in volumes: 297 volume_update_list.append( 298 {'id': volume['id'], 299 'replication_status': fields.ReplicationStatus.DISABLED}) 300 return ( 301 {'replication_status': fields.ReplicationStatus.DISABLED}, 302 volume_update_list) 303 304 def failover_replication(self, context, group, volumes, secondary_id): 305 volume_update_list = [] 306 for volume in volumes: 307 volume_update_list.append( 308 {'id': volume['id'], 309 'replication_status': fields.ReplicationStatus.FAILED_OVER}) 310 return ({'replication_status': fields.ReplicationStatus.FAILED_OVER}, 311 volume_update_list) 312 313 def get_replication_error_status(self, context, groups): 314 return( 315 [{'group_id': groups[0]['id'], 316 'replication_status': fields.ReplicationStatus.ERROR}], 317 [{'volume_id': VOLUME['id'], 318 'replication_status': fields.ReplicationStatus.ERROR}]) 319 320 321class IBMStorageVolumeDriverTest(test.TestCase): 322 """Test IBM Storage driver 323 324 Test IBM Storage driver for IBM XIV, Spectrum Accelerate, 325 FlashSystem A9000, FlashSystem A9000R and DS8000 storage Systems. 326 """ 327 328 def setUp(self): 329 """Initialize IBM Storage Driver.""" 330 super(IBMStorageVolumeDriverTest, self).setUp() 331 332 configuration = mock.Mock(conf.Configuration) 333 configuration.san_is_local = False 334 configuration.proxy = FAKE_PROXY 335 configuration.connection_type = 'iscsi' 336 configuration.chap = 'disabled' 337 configuration.san_ip = FAKE 338 configuration.management_ips = FAKE 339 configuration.san_login = FAKE 340 configuration.san_clustername = FAKE 341 configuration.san_password = FAKE 342 configuration.append_config_values(mock.ANY) 343 344 self.driver = ibm_storage.IBMStorageDriver( 345 configuration=configuration) 346 347 def test_initialized_should_set_ibm_storage_info(self): 348 """Test that the san flags are passed to the IBM proxy.""" 349 350 self.assertEqual( 351 self.driver.proxy.ibm_storage_info['user'], 352 self.driver.configuration.san_login) 353 self.assertEqual( 354 self.driver.proxy.ibm_storage_info['password'], 355 self.driver.configuration.san_password) 356 self.assertEqual( 357 self.driver.proxy.ibm_storage_info['address'], 358 self.driver.configuration.san_ip) 359 self.assertEqual( 360 self.driver.proxy.ibm_storage_info['vol_pool'], 361 self.driver.configuration.san_clustername) 362 363 def test_setup_should_fail_if_credentials_are_invalid(self): 364 """Test that the proxy validates credentials.""" 365 366 self.driver.proxy.ibm_storage_info['user'] = 'invalid' 367 self.assertRaises(exception.NotAuthorized, self.driver.do_setup, None) 368 369 def test_setup_should_fail_if_connection_is_invalid(self): 370 """Test that the proxy validates connection.""" 371 372 self.driver.proxy.ibm_storage_info['address'] = \ 373 'invalid' 374 self.assertRaises(exception.HostNotFound, self.driver.do_setup, None) 375 376 def test_create_volume(self): 377 """Test creating a volume.""" 378 379 self.driver.do_setup(None) 380 self.driver.create_volume(VOLUME) 381 has_volume = self.driver.proxy.volume_exists(VOLUME) 382 self.assertTrue(has_volume) 383 self.driver.delete_volume(VOLUME) 384 385 def test_volume_exists(self): 386 """Test the volume exist method with a volume that doesn't exist.""" 387 388 self.driver.do_setup(None) 389 390 self.assertFalse( 391 self.driver.proxy.volume_exists({'name': FAKE}) 392 ) 393 394 def test_delete_volume(self): 395 """Verify that a volume is deleted.""" 396 397 self.driver.do_setup(None) 398 self.driver.create_volume(VOLUME) 399 self.driver.delete_volume(VOLUME) 400 has_volume = self.driver.proxy.volume_exists(VOLUME) 401 self.assertFalse(has_volume) 402 403 def test_delete_volume_should_fail_for_not_existing_volume(self): 404 """Verify that deleting a non-existing volume is OK.""" 405 406 self.driver.do_setup(None) 407 self.driver.delete_volume(VOLUME) 408 409 def test_create_volume_should_fail_if_no_pool_space_left(self): 410 """Verify that the proxy validates volume pool space.""" 411 412 self.driver.do_setup(None) 413 self.assertRaises(exception.VolumeBackendAPIException, 414 self.driver.create_volume, 415 {'name': FAKE, 416 'id': 1, 417 'size': TOO_BIG_VOLUME_SIZE}) 418 419 def test_initialize_connection(self): 420 """Test that inititialize connection attaches volume to host.""" 421 422 self.driver.do_setup(None) 423 self.driver.create_volume(VOLUME) 424 self.driver.initialize_connection(VOLUME, CONNECTOR) 425 426 self.assertTrue( 427 self.driver.proxy.is_volume_attached(VOLUME, CONNECTOR)) 428 429 self.driver.terminate_connection(VOLUME, CONNECTOR) 430 self.driver.delete_volume(VOLUME) 431 432 def test_initialize_connection_should_fail_for_non_existing_volume(self): 433 """Verify that initialize won't work for non-existing volume.""" 434 435 self.driver.do_setup(None) 436 self.assertRaises(exception.VolumeNotFound, 437 self.driver.initialize_connection, 438 VOLUME, 439 CONNECTOR) 440 441 def test_terminate_connection(self): 442 """Test terminating a connection.""" 443 444 self.driver.do_setup(None) 445 self.driver.create_volume(VOLUME) 446 self.driver.initialize_connection(VOLUME, CONNECTOR) 447 self.driver.terminate_connection(VOLUME, CONNECTOR) 448 449 self.assertFalse(self.driver.proxy.is_volume_attached( 450 VOLUME, 451 CONNECTOR)) 452 453 self.driver.delete_volume(VOLUME) 454 455 def test_terminate_connection_should_fail_on_non_existing_volume(self): 456 """Test that terminate won't work for non-existing volumes.""" 457 458 self.driver.do_setup(None) 459 self.assertRaises(exception.VolumeNotFound, 460 self.driver.terminate_connection, 461 VOLUME, 462 CONNECTOR) 463 464 def test_manage_existing_get_size(self): 465 """Test that manage_existing_get_size returns the expected size. """ 466 467 self.driver.do_setup(None) 468 self.driver.create_volume(MANAGED_VOLUME) 469 existing_ref = {'source-name': MANAGED_VOLUME['name']} 470 return_size = self.driver.manage_existing_get_size( 471 VOLUME, 472 existing_ref) 473 self.assertEqual(MANAGED_VOLUME['size'], return_size) 474 475 # cover both case, whether driver renames the volume or not 476 self.driver.delete_volume(VOLUME) 477 self.driver.delete_volume(MANAGED_VOLUME) 478 479 def test_manage_existing_get_size_should_fail_on_non_existing_volume(self): 480 """Test that manage_existing_get_size fails on non existing volume. """ 481 482 self.driver.do_setup(None) 483 # on purpose - do NOT create managed volume 484 existing_ref = {'source-name': MANAGED_VOLUME['name']} 485 self.assertRaises(exception.VolumeNotFound, 486 self.driver.manage_existing_get_size, 487 VOLUME, 488 existing_ref) 489 490 def test_manage_existing(self): 491 """Test that manage_existing returns successfully. """ 492 493 self.driver.do_setup(None) 494 self.driver.create_volume(MANAGED_VOLUME) 495 existing_ref = {'source-name': MANAGED_VOLUME['name']} 496 self.driver.manage_existing(VOLUME, existing_ref) 497 self.assertEqual(MANAGED_VOLUME['size'], VOLUME['size']) 498 499 # cover both case, whether driver renames the volume or not 500 self.driver.delete_volume(VOLUME) 501 self.driver.delete_volume(MANAGED_VOLUME) 502 503 def test_manage_existing_should_fail_on_non_existing_volume(self): 504 """Test that manage_existing fails on non existing volume. """ 505 506 self.driver.do_setup(None) 507 # on purpose - do NOT create managed volume 508 existing_ref = {'source-name': MANAGED_VOLUME['name']} 509 self.assertRaises(exception.VolumeNotFound, 510 self.driver.manage_existing, 511 VOLUME, 512 existing_ref) 513 514 def test_get_replication_status(self): 515 """Test that get_replication_status return successfully. """ 516 517 self.driver.do_setup(None) 518 519 # assume the replicated volume is inactive 520 replicated_volume = copy.deepcopy(REPLICATED_VOLUME) 521 replicated_volume['replication_status'] = 'inactive' 522 model_update = self.driver.get_replication_status( 523 CONTEXT, 524 replicated_volume 525 ) 526 self.assertEqual('active', model_update['replication_status']) 527 528 def test_get_replication_status_fail_on_exception(self): 529 """Test that get_replication_status fails on exception""" 530 531 self.driver.do_setup(None) 532 533 replicated_volume = copy.deepcopy(REPLICATED_VOLUME) 534 # on purpose - set invalid value to replication_status 535 # expect an exception. 536 replicated_volume['replication_status'] = 'invalid_status_val' 537 self.assertRaises( 538 exception.CinderException, 539 self.driver.get_replication_status, 540 CONTEXT, 541 replicated_volume 542 ) 543 544 def test_retype(self): 545 """Test that retype returns successfully.""" 546 547 self.driver.do_setup(None) 548 549 # prepare parameters 550 ctxt = context.get_admin_context() 551 552 host = { 553 'host': 'foo', 554 'capabilities': { 555 'location_info': 'ibm_storage_fake_1', 556 'extent_size': '1024' 557 } 558 } 559 560 key_specs_old = {'easytier': False, 'warning': 2, 'autoexpand': True} 561 key_specs_new = {'easytier': True, 'warning': 5, 'autoexpand': False} 562 old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) 563 new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) 564 565 diff, equal = volume_types.volume_types_diff( 566 ctxt, 567 old_type_ref['id'], 568 new_type_ref['id'], 569 ) 570 571 volume = copy.deepcopy(VOLUME) 572 old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) 573 volume['volume_type'] = old_type 574 volume['host'] = host 575 new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) 576 577 self.driver.create_volume(volume) 578 ret = self.driver.retype(ctxt, volume, new_type, diff, host) 579 self.assertTrue(bool(ret)) 580 self.assertEqual('1', volume['easytier']) 581 582 def test_retype_fail_on_exception(self): 583 """Test that retype fails on exception.""" 584 585 self.driver.do_setup(None) 586 587 # prepare parameters 588 ctxt = context.get_admin_context() 589 590 host = { 591 'host': 'foo', 592 'capabilities': { 593 'location_info': 'ibm_storage_fake_1', 594 'extent_size': '1024' 595 } 596 } 597 598 key_specs_old = {'easytier': False, 'warning': 2, 'autoexpand': True} 599 old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) 600 new_type_ref = volume_types.create(ctxt, 'new') 601 602 diff, equal = volume_types.volume_types_diff( 603 ctxt, 604 old_type_ref['id'], 605 new_type_ref['id'], 606 ) 607 608 volume = copy.deepcopy(VOLUME) 609 old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) 610 volume['volume_type'] = old_type 611 volume['host'] = host 612 new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) 613 614 self.driver.create_volume(volume) 615 self.assertRaises( 616 KeyError, 617 self.driver.retype, 618 ctxt, volume, new_type, diff, host 619 ) 620 621 def test_create_group(self): 622 """Test that create_group return successfully.""" 623 624 self.driver.do_setup(None) 625 626 ctxt = context.get_admin_context() 627 628 # Create group 629 model_update = self.driver.create_group(ctxt, GROUP) 630 631 self.assertEqual(fields.GroupStatus.AVAILABLE, 632 model_update['status'], 633 "Group created failed") 634 635 def test_create_group_fail_on_group_not_empty(self): 636 """Test create_group with empty group.""" 637 638 self.driver.do_setup(None) 639 640 ctxt = context.get_admin_context() 641 642 # Create volumes 643 # And add the volumes into the group before creating group 644 self.driver.create_volume(GROUP_VOLUME) 645 646 self.assertRaises(exception.CinderException, 647 self.driver.create_group, 648 ctxt, GROUP) 649 650 def test_delete_group(self): 651 """Test that delete_group return successfully.""" 652 653 self.driver.do_setup(None) 654 655 ctxt = context.get_admin_context() 656 657 # Create group 658 self.driver.create_group(ctxt, GROUP) 659 660 # Create volumes and add them to group 661 self.driver.create_volume(GROUP_VOLUME) 662 663 # Delete group 664 model_update, volumes = self.driver.delete_group(ctxt, GROUP, 665 [GROUP_VOLUME]) 666 667 # Verify the result 668 self.assertEqual(fields.GroupStatus.DELETED, 669 model_update['status'], 670 'Group deleted failed') 671 for volume in volumes: 672 self.assertEqual('deleted', 673 volume['status'], 674 'Group deleted failed') 675 676 def test_delete_group_fail_on_volume_not_delete(self): 677 """Test delete_group with volume delete failure.""" 678 679 self.driver.do_setup(None) 680 681 ctxt = context.get_admin_context() 682 683 # Create group 684 self.driver.create_group(ctxt, GROUP) 685 686 # Set the volume not to be deleted 687 volume = copy.deepcopy(GROUP_VOLUME) 688 volume['name'] = CANNOT_DELETE 689 690 # Create volumes and add them to group 691 self.driver.create_volume(volume) 692 693 self.assertRaises(exception.VolumeBackendAPIException, 694 self.driver.delete_group, 695 ctxt, GROUP, [volume]) 696 697 def test_create_group_snapshot(self): 698 """Test that create_group_snapshot return successfully.""" 699 700 self.driver.do_setup(None) 701 702 ctxt = context.get_admin_context() 703 704 # Create group 705 self.driver.create_group(ctxt, GROUP) 706 707 # Create volumes and add them to group 708 self.driver.create_volume(VOLUME) 709 710 # Create group snapshot 711 model_update, snapshots = self.driver.create_group_snapshot( 712 ctxt, GROUP_SNAPSHOT, [VOLUME]) 713 714 # Verify the result 715 self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE, 716 model_update['status'], 717 'Group Snapshot created failed') 718 for snap in snapshots: 719 self.assertEqual('available', 720 snap['status']) 721 722 # Clean the environment 723 self.driver.delete_group_snapshot(ctxt, GROUP_SNAPSHOT, [VOLUME]) 724 self.driver.delete_group(ctxt, GROUP, [VOLUME]) 725 726 def test_create_group_snapshot_fail_on_no_pool_space_left(self): 727 """Test create_group_snapshot when no pool space left.""" 728 729 self.driver.do_setup(None) 730 731 ctxt = context.get_admin_context() 732 733 # Create group 734 self.driver.create_group(ctxt, GROUP) 735 736 # Set the volume size 737 volume = copy.deepcopy(GROUP_VOLUME) 738 volume['size'] = POOL_SIZE / 2 + 1 739 740 # Create volumes and add them to group 741 self.driver.create_volume(volume) 742 743 self.assertRaises(exception.VolumeBackendAPIException, 744 self.driver.create_group_snapshot, 745 ctxt, GROUP_SNAPSHOT, [volume]) 746 747 # Clean the environment 748 self.driver.volumes = None 749 self.driver.delete_group(ctxt, GROUP, [volume]) 750 751 def test_delete_group_snapshot(self): 752 """Test that delete_group_snapshot return successfully.""" 753 754 self.driver.do_setup(None) 755 756 ctxt = context.get_admin_context() 757 758 # Create group 759 self.driver.create_group(ctxt, GROUP) 760 761 # Create volumes and add them to group 762 self.driver.create_volume(GROUP_VOLUME) 763 764 # Create group snapshot 765 self.driver.create_group_snapshot(ctxt, GROUP_SNAPSHOT, [GROUP_VOLUME]) 766 767 # Delete group snapshot 768 model_update, snapshots = self.driver.delete_group_snapshot( 769 ctxt, GROUP_SNAPSHOT, [GROUP_VOLUME]) 770 771 # Verify the result 772 self.assertEqual(fields.GroupSnapshotStatus.DELETED, 773 model_update['status'], 774 'Group Snapshot deleted failed') 775 for snap in snapshots: 776 self.assertEqual('deleted', 777 snap['status']) 778 779 # Clean the environment 780 self.driver.delete_group(ctxt, GROUP, [GROUP_VOLUME]) 781 782 def test_delete_group_snapshot_fail_on_snapshot_not_delete(self): 783 """Test delete_group_snapshot when the snapshot cannot be deleted.""" 784 785 self.driver.do_setup(None) 786 787 ctxt = context.get_admin_context() 788 789 # Create group 790 self.driver.create_group(ctxt, GROUP) 791 792 # Set the snapshot not to be deleted 793 volume = copy.deepcopy(GROUP_VOLUME) 794 volume['name'] = CANNOT_DELETE 795 796 # Create volumes and add them to group 797 self.driver.create_volume(volume) 798 799 # Create group snapshot 800 self.driver.create_group_snapshot(ctxt, GROUP_SNAPSHOT, [volume]) 801 802 self.assertRaises(exception.VolumeBackendAPIException, 803 self.driver.delete_group_snapshot, 804 ctxt, GROUP_SNAPSHOT, [volume]) 805 806 def test_update_group_without_volumes(self): 807 """Test update_group when there are no volumes specified.""" 808 809 self.driver.do_setup(None) 810 811 ctxt = context.get_admin_context() 812 813 # Update group 814 model_update, added, removed = self.driver.update_group( 815 ctxt, GROUP, [], []) 816 817 self.assertEqual(fields.GroupStatus.AVAILABLE, 818 model_update['status'], 819 "Group update failed") 820 self.assertIsNone(added, 821 "added volumes list is not empty") 822 self.assertIsNone(removed, 823 "removed volumes list is not empty") 824 825 def test_update_group_with_volumes(self): 826 """Test update_group when there are volumes specified.""" 827 828 self.driver.do_setup(None) 829 830 ctxt = context.get_admin_context() 831 832 # Update group 833 model_update, added, removed = self.driver.update_group( 834 ctxt, GROUP, [VOLUME], [VOLUME2]) 835 836 self.assertEqual(fields.GroupStatus.AVAILABLE, 837 model_update['status'], 838 "Group update failed") 839 self.assertIsNone(added, 840 "added volumes list is not empty") 841 self.assertIsNone(removed, 842 "removed volumes list is not empty") 843 844 def test_create_group_from_src_without_volumes(self): 845 """Test create_group_from_src with no volumes specified.""" 846 847 self.driver.do_setup(None) 848 849 ctxt = context.get_admin_context() 850 851 # Create group from source 852 model_update, volumes_model_update = ( 853 self.driver.create_group_from_src( 854 ctxt, GROUP, [], GROUP_SNAPSHOT, [])) 855 856 # model_update can be None or return available in status 857 if model_update: 858 self.assertEqual(fields.GroupStatus.AVAILABLE, 859 model_update['status'], 860 "Group create from source failed") 861 # volumes_model_update can be None or return available in status 862 if volumes_model_update: 863 self.assertFalse(volumes_model_update, 864 "volumes list is not empty") 865 866 def test_create_group_from_src_with_volumes(self): 867 """Test create_group_from_src with volumes specified.""" 868 869 self.driver.do_setup(None) 870 871 ctxt = context.get_admin_context() 872 873 # Create group from source 874 model_update, volumes_model_update = ( 875 self.driver.create_group_from_src( 876 ctxt, GROUP, [VOLUME], GROUP_SNAPSHOT, [SNAPSHOT])) 877 878 # model_update can be None or return available in status 879 if model_update: 880 self.assertEqual(fields.GroupStatus.AVAILABLE, 881 model_update['status'], 882 "Group create from source failed") 883 # volumes_model_update can be None or return available in status 884 if volumes_model_update: 885 self.assertEqual(fields.GroupStatus.AVAILABLE, 886 volumes_model_update['status'], 887 "volumes list status failed") 888 889 def test_freeze_backend(self): 890 """Test that freeze_backend returns successful""" 891 892 self.driver.do_setup(None) 893 894 # not much we can test here... 895 self.assertTrue(self.driver.freeze_backend(CONTEXT)) 896 897 def test_thaw_backend(self): 898 """Test that thaw_backend returns successful""" 899 900 self.driver.do_setup(None) 901 902 # not much we can test here... 903 self.assertTrue(self.driver.thaw_backend(CONTEXT)) 904 905 def test_failover_host(self): 906 """Test that failover_host returns expected values""" 907 908 self.driver.do_setup(None) 909 910 replicated_volume = copy.deepcopy(REPLICATED_VOLUME) 911 # assume the replication_status is active 912 replicated_volume['replication_status'] = 'active' 913 914 expected_target_id = 'BLA' 915 expected_volume_update_list = [ 916 {'volume_id': REPLICATED_VOLUME['id'], 917 'updates': {'replication_status': 'failed-over'}}] 918 919 target_id, volume_update_list, __ = self.driver.failover_host( 920 CONTEXT, 921 [replicated_volume], 922 SECONDARY, 923 [] 924 ) 925 926 self.assertEqual(expected_target_id, target_id) 927 self.assertEqual(expected_volume_update_list, volume_update_list) 928 929 def test_failover_host_bad_state(self): 930 """Test that failover_host returns with error""" 931 932 self.driver.do_setup(None) 933 934 replicated_volume = copy.deepcopy(REPLICATED_VOLUME) 935 # assume the replication_status is active 936 replicated_volume['replication_status'] = 'invalid_status_val' 937 938 expected_target_id = 'BLA' 939 expected_volume_update_list = [ 940 {'volume_id': REPLICATED_VOLUME['id'], 941 'updates': {'replication_status': 'error'}}] 942 943 target_id, volume_update_list, __ = self.driver.failover_host( 944 CONTEXT, 945 [replicated_volume], 946 SECONDARY, 947 [] 948 ) 949 950 self.assertEqual(expected_target_id, target_id) 951 self.assertEqual(expected_volume_update_list, volume_update_list) 952 953 def test_enable_replication(self): 954 self.driver.do_setup(None) 955 model_update, volumes_model_update = self.driver.enable_replication( 956 CONTEXT, GROUP, [REPLICATED_VOLUME]) 957 self.assertEqual(fields.ReplicationStatus.ENABLED, 958 model_update['replication_status']) 959 for vol in volumes_model_update: 960 self.assertEqual(fields.ReplicationStatus.ENABLED, 961 vol['replication_status']) 962 963 def test_disable_replication(self): 964 self.driver.do_setup(None) 965 model_update, volumes_model_update = self.driver.disable_replication( 966 CONTEXT, GROUP, [REPLICATED_VOLUME_DISABLED]) 967 self.assertEqual(fields.ReplicationStatus.DISABLED, 968 model_update['replication_status']) 969 for vol in volumes_model_update: 970 self.assertEqual(fields.ReplicationStatus.DISABLED, 971 volumes_model_update[0]['replication_status']) 972 973 def test_failover_replication(self): 974 self.driver.do_setup(None) 975 model_update, volumes_model_update = self.driver.failover_replication( 976 CONTEXT, GROUP, [VOLUME], SECONDARY) 977 self.assertEqual(fields.ReplicationStatus.FAILED_OVER, 978 model_update['replication_status']) 979 980 def test_get_replication_error_status(self): 981 self.driver.do_setup(None) 982 group_model_updates, volume_model_updates = ( 983 self.driver.get_replication_error_status(CONTEXT, [GROUP])) 984 self.assertEqual(fields.ReplicationStatus.ERROR, 985 group_model_updates[0]['replication_status']) 986 self.assertEqual(fields.ReplicationStatus.ERROR, 987 volume_model_updates[0]['replication_status']) 988