# Copyright (c) 2017-2019 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from copy import deepcopy from unittest import mock from cinder import exception from cinder import test from cinder.tests.unit.volume.drivers.dell_emc.powermax import ( powermax_data as tpd) from cinder.tests.unit.volume.drivers.dell_emc.powermax import ( powermax_fake_objects as tpfo) from cinder.volume.drivers.dell_emc.powermax import common from cinder.volume.drivers.dell_emc.powermax import masking from cinder.volume.drivers.dell_emc.powermax import provision from cinder.volume.drivers.dell_emc.powermax import rest from cinder.volume.drivers.dell_emc.powermax import utils from cinder.volume import volume_utils class PowerMaxMaskingTest(test.TestCase): def setUp(self): self.data = tpd.PowerMaxData() super(PowerMaxMaskingTest, self).setUp() volume_utils.get_max_over_subscription_ratio = mock.Mock() configuration = mock.Mock() configuration.safe_get.return_value = 'MaskingTests' configuration.config_group = 'MaskingTests' self._gather_info = common.PowerMaxCommon._gather_info common.PowerMaxCommon._get_u4p_failover_info = mock.Mock() common.PowerMaxCommon._gather_info = mock.Mock() rest.PowerMaxRest._establish_rest_session = mock.Mock( return_value=tpfo.FakeRequestsSession()) driver = common.PowerMaxCommon( 'iSCSI', self.data.version, configuration=configuration) driver_fc = common.PowerMaxCommon( 'FC', self.data.version, configuration=configuration) self.driver = driver self.driver_fc = driver_fc self.mask = self.driver.masking self.extra_specs = self.data.extra_specs self.extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_i self.maskingviewdict = self.driver._populate_masking_dict( self.data.test_volume, self.data.connector, self.extra_specs) self.maskingviewdict['extra_specs'] = self.extra_specs self.maskingviewdict[utils.IS_MULTIATTACH] = False self.device_id = self.data.device_id self.volume_name = self.data.volume_details[0]['volume_identifier'] def tearDown(self): super(PowerMaxMaskingTest, self).tearDown() common.PowerMaxCommon._gather_info = self._gather_info def test_sanity_port_group_check_none(self): self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking._sanity_port_group_check, None, self.data.array) @mock.patch.object(rest.PowerMaxRest, 'get_portgroup', return_value=None) def test_sanity_port_group_check_invalid_portgroup(self, mock_pg): self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking._sanity_port_group_check, None, self.data.array) @mock.patch.object(rest.PowerMaxRest, 'get_portgroup', return_value=tpd.PowerMaxData.portgroup) def test_sanity_port_group_check(self, mock_pg): self.driver.masking._sanity_port_group_check( self.data.port_group_name_f, self.data.array) @mock.patch.object(masking.PowerMaxMasking, 'get_or_create_masking_view_and_map_lun') def test_setup_masking_view(self, mock_get_or_create_mv): self.driver.masking.setup_masking_view( self.data.array, self.data.test_volume, self.maskingviewdict, self.extra_specs) mock_get_or_create_mv.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, '_check_adding_volume_to_storage_group') @mock.patch.object(masking.PowerMaxMasking, '_move_vol_from_default_sg', return_value=None) @mock.patch.object(masking.PowerMaxMasking, '_get_or_create_masking_view', side_effect=[None, 'Error in masking view retrieval', exception.VolumeBackendAPIException]) @mock.patch.object(rest.PowerMaxRest, 'get_element_from_masking_view', side_effect=[tpd.PowerMaxData.port_group_name_i, Exception('Exception')]) def test_get_or_create_masking_view_and_map_lun( self, mock_masking_view_element, mock_masking, mock_move, mock_add_volume): rollback_dict = ( self.driver.masking.get_or_create_masking_view_and_map_lun( self.data.array, self.data.test_volume, self.maskingviewdict['maskingview_name'], self.maskingviewdict, self.extra_specs)) self.assertEqual(self.maskingviewdict, rollback_dict) self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking.get_or_create_masking_view_and_map_lun, self.data.array, self.data.test_volume, self.maskingviewdict['maskingview_name'], self.maskingviewdict, self.extra_specs) self.maskingviewdict['slo'] = None self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking.get_or_create_masking_view_and_map_lun, self.data.array, self.data.test_volume, self.maskingviewdict['maskingview_name'], self.maskingviewdict, self.extra_specs) @mock.patch.object(masking.PowerMaxMasking, '_check_adding_volume_to_storage_group', return_value=None) @mock.patch.object( rest.PowerMaxRest, 'move_volume_between_storage_groups', side_effect=[None, exception.VolumeBackendAPIException(data='')]) @mock.patch.object(rest.PowerMaxRest, 'is_volume_in_storagegroup', side_effect=[True, False, True]) def test_move_vol_from_default_sg( self, mock_volume_in_sg, mock_move_volume, mock_add): msg = None for x in range(0, 2): msg = self.driver.masking._move_vol_from_default_sg( self.data.array, self.device_id, self.volume_name, self.data.defaultstoragegroup_name, self.data.storagegroup_name_i, self.extra_specs) mock_move_volume.assert_called_once() mock_add.assert_called_once() self.assertIsNone(msg) msg = self.driver.masking._move_vol_from_default_sg( self.data.array, self.device_id, self.volume_name, self.data.defaultstoragegroup_name, self.data.storagegroup_name_i, self.extra_specs) self.assertIsNotNone(msg) @mock.patch.object(rest.PowerMaxRest, 'remove_child_sg_from_parent_sg') @mock.patch.object(masking.PowerMaxMasking, 'get_parent_sg_from_child', side_effect=[None, tpd.PowerMaxData.parent_sg_f]) @mock.patch.object( rest.PowerMaxRest, 'get_num_vols_in_sg', side_effect=[2, 1, 1]) def test_move_volume_between_storage_groups( self, mock_num, mock_parent, mock_rm): for x in range(0, 3): self.driver.masking.move_volume_between_storage_groups( self.data.array, self.data.device_id, self.data.storagegroup_name_i, self.data.storagegroup_name_f, self.data.extra_specs) mock_rm.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'get_masking_view', side_effect=[tpd.PowerMaxData.maskingview, tpd.PowerMaxData.maskingview, None]) @mock.patch.object( masking.PowerMaxMasking, '_validate_existing_masking_view', side_effect=[(tpd.PowerMaxData.maskingview[1]['storageGroupId'], None), (None, 'Error Message')]) @mock.patch.object(masking.PowerMaxMasking, '_create_new_masking_view', return_value=None) def test_get_or_create_masking_view(self, mock_create_mv, mock_validate_mv, mock_get_mv): for x in range(0, 3): self.driver.masking._get_or_create_masking_view( self.data.array, self.maskingviewdict, self.data.defaultstoragegroup_name, self.extra_specs) mock_create_mv.assert_called_once() @mock.patch.object( masking.PowerMaxMasking, '_get_or_create_initiator_group', side_effect=[(None, 'Initiator group error'), (None, None), (None, None), (None, None), (None, None), (None, None), (None, None), (None, None)]) @mock.patch.object( masking.PowerMaxMasking, '_get_or_create_storage_group', side_effect=['Storage group not found', None, 'Storage group not found', 'Storage group not found', None, None, None, None, None, None, None]) @mock.patch.object( masking.PowerMaxMasking, '_move_vol_from_default_sg', side_effect=['Storage group error', None, 'Storage group error', None]) @mock.patch.object( masking.PowerMaxMasking, 'create_masking_view', return_value=None) def test_create_new_masking_view( self, mock_create_mv, mock_move, mock_create_SG, mock_create_IG): for x in range(0, 6): self.driver.masking._create_new_masking_view( self.data.array, self.maskingviewdict, self.maskingviewdict['maskingview_name'], self.data.defaultstoragegroup_name, self.extra_specs) mock_create_mv.assert_called_once() @mock.patch.object( masking.PowerMaxMasking, '_check_existing_storage_group', side_effect=[(tpd.PowerMaxData.storagegroup_name_i, None), (tpd.PowerMaxData.storagegroup_name_i, None), (None, 'Error Checking existing storage group')]) @mock.patch.object( rest.PowerMaxRest, 'get_element_from_masking_view', return_value=tpd.PowerMaxData.port_group_name_i) @mock.patch.object( masking.PowerMaxMasking, '_check_port_group', side_effect=[(None, None), (None, 'Error checking pg')]) @mock.patch.object( masking.PowerMaxMasking, '_check_existing_initiator_group', return_value=(tpd.PowerMaxData.initiatorgroup_name_i, None)) def test_validate_existing_masking_view( self, mock_check_ig, mock_check_pg, mock_get_mv_element, mock_check_sg): for x in range(0, 3): self.driver.masking._validate_existing_masking_view( self.data.array, self.maskingviewdict, self.maskingviewdict['maskingview_name'], self.data.defaultstoragegroup_name, self.extra_specs) self.assertEqual(3, mock_check_sg.call_count) mock_get_mv_element.assert_called_with( self.data.array, self.maskingviewdict['maskingview_name'], portgroup=True) mock_check_ig.assert_called_once() @mock.patch.object( rest.PowerMaxRest, 'get_storage_group', side_effect=[tpd.PowerMaxData.storagegroup_name_i, None, tpd.PowerMaxData.storagegroup_name_i]) @mock.patch.object( provision.PowerMaxProvision, 'create_storage_group', side_effect=[tpd.PowerMaxData.storagegroup_name_i, None]) def test_get_or_create_storage_group(self, mock_sg, mock_get_sg): for x in range(0, 2): self.driver.masking._get_or_create_storage_group( self.data.array, self.maskingviewdict, self.data.storagegroup_name_i, self.extra_specs) self.assertEqual(3, mock_get_sg.call_count) self.assertEqual(1, mock_sg.call_count) @mock.patch.object( rest.PowerMaxRest, 'get_storage_group', side_effect=[None, tpd.PowerMaxData.storagegroup_name_i]) @mock.patch.object( provision.PowerMaxProvision, 'create_storage_group', side_effect=[tpd.PowerMaxData.storagegroup_name_i]) def test_get_or_create_storage_group_is_parent(self, mock_sg, mock_get_sg): self.driver.masking._get_or_create_storage_group( self.data.array, self.maskingviewdict, self.data.storagegroup_name_i, self.extra_specs, True) self.assertEqual(2, mock_get_sg.call_count) self.assertEqual(1, mock_sg.call_count) @mock.patch.object(masking.PowerMaxMasking, '_move_vol_from_default_sg', return_value=None) @mock.patch.object(masking.PowerMaxMasking, '_get_or_create_storage_group', return_value=None) @mock.patch.object(rest.PowerMaxRest, 'get_element_from_masking_view', return_value=tpd.PowerMaxData.parent_sg_i) @mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg', side_effect=[True, False]) @mock.patch.object(masking.PowerMaxMasking, '_check_add_child_sg_to_parent_sg', return_value=None) def test_check_existing_storage_group_success( self, mock_add_sg, mock_is_child, mock_get_mv_element, mock_create_sg, mock_move): masking_view_dict = deepcopy(self.data.masking_view_dict) masking_view_dict['extra_specs'] = self.data.extra_specs with mock.patch.object( self.driver.rest, 'get_storage_group', side_effect=[tpd.PowerMaxData.parent_sg_i, tpd.PowerMaxData.storagegroup_name_i]): _, msg = (self.driver.masking._check_existing_storage_group( self.data.array, self.maskingviewdict['maskingview_name'], self.data.defaultstoragegroup_name, masking_view_dict, self.data.extra_specs)) self.assertIsNone(msg) mock_create_sg.assert_not_called() with mock.patch.object(self.driver.rest, 'get_storage_group', side_effect=[ tpd.PowerMaxData.parent_sg_i, None]): _, msg = (self.driver.masking._check_existing_storage_group( self.data.array, self.maskingviewdict['maskingview_name'], self.data.defaultstoragegroup_name, masking_view_dict, self.data.extra_specs)) self.assertIsNone(msg) mock_create_sg.assert_called_once_with( self.data.array, masking_view_dict, tpd.PowerMaxData.storagegroup_name_f, self.data.extra_specs) @mock.patch.object(masking.PowerMaxMasking, '_move_vol_from_default_sg', side_effect=[None, 'Error Message']) @mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg', side_effect=[True, False, False]) @mock.patch.object(rest.PowerMaxRest, 'get_element_from_masking_view', return_value=tpd.PowerMaxData.parent_sg_i) @mock.patch.object(rest.PowerMaxRest, 'get_storage_group', side_effect=[ None, tpd.PowerMaxData.parent_sg_i, None, tpd.PowerMaxData.parent_sg_i, None, tpd.PowerMaxData.parent_sg_i, None]) def test_check_existing_storage_group_failed( self, mock_get_sg, mock_get_mv_element, mock_child, mock_move): masking_view_dict = deepcopy(self.data.masking_view_dict) masking_view_dict['extra_specs'] = self.data.extra_specs for x in range(0, 4): _, msg = (self.driver.masking._check_existing_storage_group( self.data.array, self.maskingviewdict['maskingview_name'], self.data.defaultstoragegroup_name, masking_view_dict, self.data.extra_specs)) self.assertIsNotNone(msg) self.assertEqual(7, mock_get_sg.call_count) self.assertEqual(1, mock_move.call_count) @mock.patch.object( rest.PowerMaxRest, 'get_portgroup', side_effect=([tpd.PowerMaxData.port_group_name_i, None])) def test_check_port_group( self, mock_get_pg): for x in range(0, 2): _, msg = self.driver.masking._check_port_group( self.data.array, self.maskingviewdict['maskingview_name']) self.assertIsNotNone(msg) self.assertEqual(2, mock_get_pg.call_count) @mock.patch.object( masking.PowerMaxMasking, '_find_initiator_group', side_effect=[tpd.PowerMaxData.initiatorgroup_name_i, None, None]) @mock.patch.object( masking.PowerMaxMasking, '_create_initiator_group', side_effect=([tpd.PowerMaxData.initiatorgroup_name_i, None])) def test_get_or_create_initiator_group(self, mock_create_ig, mock_find_ig): self.driver.masking._get_or_create_initiator_group( self.data.array, self.data.initiatorgroup_name_i, self.data.connector, self.extra_specs) mock_create_ig.assert_not_called() found_init_group, msg = ( self.driver.masking._get_or_create_initiator_group( self.data.array, self.data.initiatorgroup_name_i, self.data.connector, self.extra_specs)) self.assertIsNone(msg) found_init_group, msg = ( self.driver.masking._get_or_create_initiator_group( self.data.array, self.data.initiatorgroup_name_i, self.data.connector, self.extra_specs)) self.assertIsNotNone(msg) def test_check_existing_initiator_group(self): with mock.patch.object( rest.PowerMaxRest, 'get_element_from_masking_view', return_value=tpd.PowerMaxData.inititiatorgroup): ig_from_mv, msg = ( self.driver.masking._check_existing_initiator_group( self.data.array, self.maskingviewdict['maskingview_name'], self.maskingviewdict, self.data.storagegroup_name_i, self.data.port_group_name_i, self.extra_specs)) self.assertEqual(self.data.inititiatorgroup, ig_from_mv) def test_check_adding_volume_to_storage_group(self): with mock.patch.object( masking.PowerMaxMasking, '_create_initiator_group'): with mock.patch.object( rest.PowerMaxRest, 'is_volume_in_storagegroup', side_effect=[True, False]): msg = ( self.driver.masking._check_adding_volume_to_storage_group( self.data.array, self.device_id, self.data.storagegroup_name_i, self.maskingviewdict[utils.VOL_NAME], self.maskingviewdict[utils.EXTRA_SPECS])) self.assertIsNone(msg) msg = ( self.driver.masking._check_adding_volume_to_storage_group( self.data.array, self.device_id, self.data.storagegroup_name_i, self.maskingviewdict[utils.VOL_NAME], self.maskingviewdict[utils.EXTRA_SPECS])) @mock.patch.object(rest.PowerMaxRest, 'add_vol_to_sg') def test_add_volume_to_storage_group(self, mock_add_volume): self.driver.masking.add_volume_to_storage_group( self.data.array, self.device_id, self.data.storagegroup_name_i, self.volume_name, self.extra_specs) mock_add_volume.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'remove_vol_from_sg') def test_remove_vol_from_storage_group(self, mock_remove_volume): with mock.patch.object( rest.PowerMaxRest, 'is_volume_in_storagegroup', side_effect=[False, True]): self.driver.masking.remove_vol_from_storage_group( self.data.array, self.device_id, self.data.storagegroup_name_i, self.volume_name, self.extra_specs) mock_remove_volume.assert_called_once() self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking.remove_vol_from_storage_group, self.data.array, self.device_id, self.data.storagegroup_name_i, self.volume_name, self.extra_specs) def test_find_initiator_names(self): foundinitiatornames = self.driver.masking.find_initiator_names( self.data.connector) self.assertEqual(self.data.connector['initiator'], foundinitiatornames[0]) foundinitiatornames = self.driver_fc.masking.find_initiator_names( self.data.connector) self.assertEqual(self.data.connector['wwpns'][0], foundinitiatornames[0]) connector = {'ip': self.data.ip, 'initiator': None, 'host': 'HostX'} self.assertRaises( exception.VolumeBackendAPIException, self.driver.masking.find_initiator_names, connector) self.assertRaises( exception.VolumeBackendAPIException, self.driver_fc.masking.find_initiator_names, connector) def test_find_initiator_group_found(self): with mock.patch.object( rest.PowerMaxRest, 'get_initiator_list', return_value=self.data.initiator_list[2]['initiatorId']): with mock.patch.object( rest.PowerMaxRest, 'get_initiator_group_from_initiator', return_value=self.data.initiator_list): found_init_group_nam = ( self.driver.masking._find_initiator_group( self.data.array, ['FA-1D:4:123456789012345'])) self.assertEqual(self.data.initiator_list, found_init_group_nam) def test_find_initiator_group_not_found(self): with mock.patch.object( rest.PowerMaxRest, 'get_initiator_list', return_value=self.data.initiator_list[2]['initiatorId']): with mock.patch.object( rest.PowerMaxRest, 'get_initiator_group_from_initiator', return_value=None): found_init_group_nam = ( self.driver.masking._find_initiator_group( self.data.array, ['Error'])) self.assertIsNone(found_init_group_nam) def test_create_masking_view(self): with mock.patch.object(rest.PowerMaxRest, 'create_masking_view', side_effect=[None, Exception]): error_message = self.driver.masking.create_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.data.storagegroup_name_i, self.data.port_group_name_i, self.data.initiatorgroup_name_i, self.extra_specs) self.assertIsNone(error_message) error_message = self.driver.masking.create_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.data.storagegroup_name_i, self.data.port_group_name_i, self.data.initiatorgroup_name_i, self.extra_specs) self.assertIsNotNone(error_message) @mock.patch.object(masking.PowerMaxMasking, '_return_volume_to_fast_managed_group') @mock.patch.object(masking.PowerMaxMasking, '_check_ig_rollback') def test_check_if_rollback_action_for_masking_required( self, mock_check_ig, mock_return): with mock.patch.object(rest.PowerMaxRest, 'get_storage_groups_from_volume', side_effect=[ exception.VolumeBackendAPIException, self.data.storagegroup_list, self.data.storagegroup_list, None, None, ]): self.assertRaises( exception.VolumeBackendAPIException, self.mask.check_if_rollback_action_for_masking_required, self.data.array, self.data.test_volume, self.device_id, self.maskingviewdict) with mock.patch.object(masking.PowerMaxMasking, 'remove_and_reset_members'): self.maskingviewdict[ 'default_sg_name'] = self.data.defaultstoragegroup_name self.mask.check_if_rollback_action_for_masking_required( self.data.array, self.data.test_volume, self.device_id, self.maskingviewdict) # Multiattach case self.mask.check_if_rollback_action_for_masking_required( self.data.array, self.data.test_volume, self.device_id, self.data.masking_view_dict_multiattach) mock_return.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'delete_masking_view') @mock.patch.object(rest.PowerMaxRest, 'delete_initiator_group') @mock.patch.object(rest.PowerMaxRest, 'get_initiator_group') @mock.patch.object( masking.PowerMaxMasking, '_find_initiator_group', return_value=tpd.PowerMaxData.initiatorgroup_name_i) def test_verify_initiator_group_from_masking_view( self, mock_find_ig, mock_get_ig, mock_delete_ig, mock_delete_mv): self.mask._verify_initiator_group_from_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.maskingviewdict, self.data.initiatorgroup_name_i, self.data.storagegroup_name_i, self.data.port_group_name_i, self.extra_specs) mock_get_ig.assert_not_called() mock_get_ig.return_value = False self.mask._verify_initiator_group_from_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.maskingviewdict, 'OS-Wrong-Host-I-IG', self.data.storagegroup_name_i, self.data.port_group_name_i, self.extra_specs) mock_get_ig.assert_called() @mock.patch.object(rest.PowerMaxRest, 'delete_masking_view') @mock.patch.object(rest.PowerMaxRest, 'delete_initiator_group') @mock.patch.object(rest.PowerMaxRest, 'get_initiator_group', return_value=True) @mock.patch.object( masking.PowerMaxMasking, '_find_initiator_group', return_value=tpd.PowerMaxData.initiatorgroup_name_i) def test_verify_initiator_group_from_masking_view2( self, mock_find_ig, mock_get_ig, mock_delete_ig, mock_delete_mv): mock_delete_mv.side_effect = [None, Exception] self.mask._verify_initiator_group_from_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.maskingviewdict, 'OS-Wrong-Host-I-IG', self.data.storagegroup_name_i, self.data.port_group_name_i, self.extra_specs) mock_delete_mv.assert_called() _, found_ig_from_connector = ( self.mask._verify_initiator_group_from_masking_view( self.data.array, self.maskingviewdict['maskingview_name'], self.maskingviewdict, 'OS-Wrong-Host-I-IG', self.data.storagegroup_name_i, self.data.port_group_name_i, self.extra_specs)) self.assertEqual(self.data.initiatorgroup_name_i, found_ig_from_connector) @mock.patch.object(rest.PowerMaxRest, 'create_initiator_group') def test_create_initiator_group(self, mock_create_ig): initiator_names = self.mask.find_initiator_names(self.data.connector) ret_init_group_name = self.mask._create_initiator_group( self.data.array, self.data.initiatorgroup_name_i, initiator_names, self.extra_specs) self.assertEqual(self.data.initiatorgroup_name_i, ret_init_group_name) @mock.patch.object(rest.PowerMaxRest, 'create_initiator_group', side_effect=([exception.VolumeBackendAPIException( masking.CREATE_IG_ERROR)])) def test_create_initiator_group_exception(self, mock_create_ig): initiator_names = self.mask.find_initiator_names(self.data.connector) self.assertRaises( exception.VolumeBackendAPIException, self.mask._create_initiator_group, self.data.array, self.data.initiatorgroup_name_i, initiator_names, self.extra_specs) @mock.patch.object(masking.PowerMaxMasking, '_last_volume_delete_initiator_group') def test_check_ig_rollback(self, mock_last_volume): with mock.patch.object( masking.PowerMaxMasking, '_find_initiator_group', side_effect=[None, 'FAKE-I-IG', self.data.initiatorgroup_name_i]): for x in range(0, 2): self.mask._check_ig_rollback(self.data.array, self.data.initiatorgroup_name_i, self.data.connector) mock_last_volume.assert_not_called() self.mask._check_ig_rollback( self.data.array, self.data.initiatorgroup_name_i, self.data.connector) mock_last_volume.assert_called() @mock.patch.object(masking.PowerMaxMasking, '_cleanup_deletion') def test_remove_and_reset_members(self, mock_cleanup): self.mask.remove_and_reset_members( self.data.array, self.device_id, self.data.test_volume, self.volume_name, self.extra_specs, reset=False) mock_cleanup.assert_called_once() @mock.patch.object( rest.PowerMaxRest, 'get_storage_groups_from_volume', side_effect=[[tpd.PowerMaxData.storagegroup_name_i], [tpd.PowerMaxData.storagegroup_name_i], [tpd.PowerMaxData.storagegroup_name_i, tpd.PowerMaxData.storagegroup_name_f]]) @mock.patch.object(masking.PowerMaxMasking, 'remove_volume_from_sg') @mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_default_storage_group') def test_cleanup_deletion(self, mock_add, mock_remove_vol, mock_get_sg): self.mask._cleanup_deletion( self.data.array, self.data.test_volume, self.device_id, self.volume_name, self.extra_specs, None, True, None) mock_add.assert_not_called() self.mask._cleanup_deletion( self.data.array, self.data.test_volume, self.device_id, self.volume_name, self.extra_specs, self.data.connector, True, None) mock_add.assert_not_called() self.mask._cleanup_deletion( self.data.array, self.data.test_volume, self.device_id, self.volume_name, self.extra_specs, None, True, None) mock_add.assert_called_once_with( self.data.array, self.device_id, self.volume_name, self.extra_specs, volume=self.data.test_volume) @mock.patch.object(masking.PowerMaxMasking, '_last_vol_in_sg') @mock.patch.object(masking.PowerMaxMasking, '_multiple_vols_in_sg') def test_remove_volume_from_sg(self, mock_multiple_vols, mock_last_vol): with mock.patch.object( rest.PowerMaxRest, 'get_masking_views_from_storage_group', return_value=None): with mock.patch.object( rest.PowerMaxRest, 'get_num_vols_in_sg', side_effect=[2, 1]): self.mask.remove_volume_from_sg( self.data.array, self.device_id, self.volume_name, self.data.defaultstoragegroup_name, self.extra_specs) mock_last_vol.assert_not_called() self.mask.remove_volume_from_sg( self.data.array, self.device_id, self.volume_name, self.data.defaultstoragegroup_name, self.extra_specs) mock_last_vol.assert_called() @mock.patch.object(masking.PowerMaxMasking, '_last_vol_in_sg') @mock.patch.object(masking.PowerMaxMasking, '_multiple_vols_in_sg') def test_remove_volume_from_sg_2(self, mock_multiple_vols, mock_last_vol): with mock.patch.object(rest.PowerMaxRest, 'is_volume_in_storagegroup', return_value=True): with mock.patch.object( rest.PowerMaxRest, 'get_masking_views_from_storage_group', return_value=[self.data.masking_view_name_i]): with mock.patch.object( rest.PowerMaxRest, 'get_num_vols_in_sg', side_effect=[2, 1]): self.mask.remove_volume_from_sg( self.data.array, self.device_id, self.volume_name, self.data.storagegroup_name_i, self.extra_specs) mock_last_vol.assert_not_called() self.mask.remove_volume_from_sg( self.data.array, self.device_id, self.volume_name, self.data.storagegroup_name_i, self.extra_specs) mock_last_vol.assert_called() @mock.patch.object(masking.PowerMaxMasking, '_last_vol_masking_views', return_value=True) @mock.patch.object(masking.PowerMaxMasking, '_last_vol_no_masking_views', return_value=True) def test_last_vol_in_sg(self, mock_no_mv, mock_mv): mv_list = [self.data.masking_view_name_i, self.data.masking_view_name_f] with mock.patch.object(rest.PowerMaxRest, 'get_masking_views_from_storage_group', side_effect=[mv_list, []]): for x in range(0, 2): self.mask._last_vol_in_sg( self.data.array, self.device_id, self.volume_name, self.data.storagegroup_name_i, self.extra_specs, self.data.connector) self.assertEqual(1, mock_mv.call_count) self.assertEqual(1, mock_no_mv.call_count) @mock.patch.object(masking.PowerMaxMasking, '_remove_last_vol_and_delete_sg') @mock.patch.object(masking.PowerMaxMasking, '_delete_cascaded_storage_groups') @mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg', side_effect=[1, 3]) @mock.patch.object(rest.PowerMaxRest, 'delete_storage_group') @mock.patch.object(masking.PowerMaxMasking, 'get_parent_sg_from_child', side_effect=[None, 'parent_sg_name', 'parent_sg_name']) def test_last_vol_no_masking_views( self, mock_get_parent, mock_delete, mock_num_vols, mock_delete_casc, mock_remove): for x in range(0, 3): self.mask._last_vol_no_masking_views( self.data.array, self.data.storagegroup_name_i, self.device_id, self.volume_name, self.extra_specs, False) self.assertEqual(1, mock_delete.call_count) self.assertEqual(1, mock_delete_casc.call_count) self.assertEqual(1, mock_remove.call_count) @mock.patch.object(masking.PowerMaxMasking, '_remove_last_vol_and_delete_sg') @mock.patch.object(masking.PowerMaxMasking, '_delete_mv_ig_and_sg') @mock.patch.object(masking.PowerMaxMasking, '_get_num_vols_from_mv', side_effect=[(1, 'parent_name'), (3, 'parent_name')]) def test_last_vol_masking_views( self, mock_num_vols, mock_delete_all, mock_remove): for x in range(0, 2): self.mask._last_vol_masking_views( self.data.array, self.data.storagegroup_name_i, [self.data.masking_view_name_i], self.device_id, self.volume_name, self.extra_specs, self.data.connector, True) self.assertEqual(1, mock_delete_all.call_count) self.assertEqual(1, mock_remove.call_count) @mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_default_storage_group') @mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg') @mock.patch.object(masking.PowerMaxMasking, 'remove_vol_from_storage_group') def test_multiple_vols_in_sg(self, mock_remove_vol, mock_get_volumes, mock_add): self.mask._multiple_vols_in_sg( self.data.array, self.device_id, self.data.storagegroup_name_i, self.volume_name, self.extra_specs, False) mock_remove_vol.assert_called_once() self.mask._multiple_vols_in_sg( self.data.array, self.device_id, self.data.storagegroup_name_i, self.volume_name, self.extra_specs, True) mock_add.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'get_element_from_masking_view') @mock.patch.object(masking.PowerMaxMasking, '_last_volume_delete_masking_view') @mock.patch.object(masking.PowerMaxMasking, '_last_volume_delete_initiator_group') @mock.patch.object(masking.PowerMaxMasking, '_delete_cascaded_storage_groups') def test_delete_mv_ig_and_sg(self, mock_delete_sg, mock_delete_ig, mock_delete_mv, mock_get_element): self.mask._delete_mv_ig_and_sg( self.data.array, self.data.device_id, self.data.masking_view_name_i, self.data.storagegroup_name_i, self.data.parent_sg_i, self.data.connector, True, self.data.extra_specs) mock_delete_sg.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'delete_masking_view') def test_last_volume_delete_masking_view(self, mock_delete_mv): self.mask._last_volume_delete_masking_view( self.data.array, self.data.masking_view_name_i) mock_delete_mv.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, 'return_volume_to_volume_group') @mock.patch.object(rest.PowerMaxRest, 'move_volume_between_storage_groups') @mock.patch.object(masking.PowerMaxMasking, 'get_or_create_default_storage_group') @mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_storage_group') def test_add_volume_to_default_storage_group( self, mock_add_sg, mock_get_sg, mock_move, mock_return): self.mask.add_volume_to_default_storage_group( self.data.array, self.device_id, self.volume_name, self.extra_specs) mock_add_sg.assert_called_once() self.mask.add_volume_to_default_storage_group( self.data.array, self.device_id, self.volume_name, self.extra_specs, src_sg=self.data.storagegroup_name_i) mock_move.assert_called_once() vol_grp_member = deepcopy(self.data.test_volume) vol_grp_member.group_id = self.data.test_vol_grp_name_id_only self.mask.add_volume_to_default_storage_group( self.data.array, self.device_id, self.volume_name, self.extra_specs, volume=vol_grp_member) mock_return.assert_called_once() def test_add_volume_to_default_storage_group_next_gen(self): with mock.patch.object(rest.PowerMaxRest, 'is_next_gen_array', return_value=True): with mock.patch.object( self.mask, 'get_or_create_default_storage_group') as mock_get: self.mask.add_volume_to_default_storage_group( self.data.array, self.device_id, self.volume_name, self.extra_specs) mock_get.assert_called_once_with( self.data.array, self.data.srp, self.extra_specs[utils.SLO], 'NONE', self.extra_specs, False, False, None) @mock.patch.object(provision.PowerMaxProvision, 'create_storage_group') def test_get_or_create_default_storage_group(self, mock_create_sg): with mock.patch.object( rest.PowerMaxRest, 'get_vmax_default_storage_group', return_value=(None, self.data.storagegroup_name_i)): storage_group_name = self.mask.get_or_create_default_storage_group( self.data.array, self.data.srp, self.data.slo, self.data.workload, self.extra_specs) self.assertEqual(self.data.storagegroup_name_i, storage_group_name) with mock.patch.object( rest.PowerMaxRest, 'get_vmax_default_storage_group', return_value=('test_sg', self.data.storagegroup_name_i)): with mock.patch.object( rest.PowerMaxRest, 'get_masking_views_from_storage_group', return_value=self.data.masking_view_name_i): self.assertRaises( exception.VolumeBackendAPIException, self.mask.get_or_create_default_storage_group, self.data.array, self.data.srp, self.data.slo, self.data.workload, self.extra_specs) @mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_default_storage_group') @mock.patch.object(rest.PowerMaxRest, 'remove_child_sg_from_parent_sg') @mock.patch.object(rest.PowerMaxRest, 'delete_storage_group') @mock.patch.object(masking.PowerMaxMasking, 'remove_vol_from_storage_group') def test_remove_last_vol_and_delete_sg(self, mock_vol_sg, mock_delete_sg, mock_rm, mock_add): self.mask._remove_last_vol_and_delete_sg( self.data.array, self.device_id, self.volume_name, self.data.storagegroup_name_i, self.extra_specs) self.mask._remove_last_vol_and_delete_sg( self.data.array, self.device_id, self.volume_name, self.data.storagegroup_name_i, self.extra_specs, self.data.parent_sg_i, True) self.assertEqual(2, mock_delete_sg.call_count) self.assertEqual(1, mock_vol_sg.call_count) self.assertEqual(1, mock_rm.call_count) self.assertEqual(1, mock_add.call_count) @mock.patch.object(rest.PowerMaxRest, 'delete_initiator_group') def test_last_volume_delete_initiator_group(self, mock_delete_ig): self.mask._last_volume_delete_initiator_group( self.data.array, self.data.initiatorgroup_name_f, 'Wrong_Host') mock_delete_ig.assert_not_called() self.mask._last_volume_delete_initiator_group( self.data.array, self.data.initiatorgroup_name_f, None) mock_delete_ig.assert_not_called() mv_list = [self.data.masking_view_name_i, self.data.masking_view_name_f] with mock.patch.object( rest.PowerMaxRest, 'get_masking_views_by_initiator_group', side_effect=[mv_list, []]): self.mask._last_volume_delete_initiator_group( self.data.array, self.data.initiatorgroup_name_i, self.data.connector['host']) mock_delete_ig.assert_not_called() self.mask._last_volume_delete_initiator_group( self.data.array, self.data.initiatorgroup_name_i, self.data.connector['host']) mock_delete_ig.assert_called_once() def test_populate_masking_dict_init_check_false(self): extra_specs = self.data.extra_specs connector = self.data.connector with mock.patch.object(self.driver, '_get_initiator_check_flag', return_value=False): masking_view_dict = self.driver._populate_masking_dict( self.data.test_volume, connector, extra_specs) self.assertFalse(masking_view_dict['initiator_check']) def test_populate_masking_dict_init_check_true(self): extra_specs = self.data.extra_specs connector = self.data.connector with mock.patch.object(self.driver, '_get_initiator_check_flag', return_value=True): masking_view_dict = self.driver._populate_masking_dict( self.data.test_volume, connector, extra_specs) self.assertTrue(masking_view_dict['initiator_check']) def test_check_existing_initiator_group_verify_true(self): mv_dict = deepcopy(self.data.masking_view_dict) mv_dict['initiator_check'] = True with mock.patch.object( rest.PowerMaxRest, 'get_element_from_masking_view', return_value=tpd.PowerMaxData.initiatorgroup_name_f): with mock.patch.object( self.mask, '_verify_initiator_group_from_masking_view', return_value=( True, self.data.initiatorgroup_name_f)) as mock_verify: self.mask._check_existing_initiator_group( self.data.array, self.data.masking_view_name_f, mv_dict, self.data.storagegroup_name_f, self.data.port_group_name_f, self.data.extra_specs) mock_verify.assert_called_once_with( self.data.array, self.data.masking_view_name_f, mv_dict, self.data.initiatorgroup_name_f, self.data.storagegroup_name_f, self.data.port_group_name_f, self.data.extra_specs) @mock.patch.object( masking.PowerMaxMasking, 'add_child_sg_to_parent_sg', side_effect=[None, exception.VolumeBackendAPIException]) @mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg', side_effect=[True, False, False]) def test_check_add_child_sg_to_parent_sg(self, mock_is_child, mock_add): for x in range(0, 3): message = self.mask._check_add_child_sg_to_parent_sg( self.data.array, self.data.storagegroup_name_i, self.data.parent_sg_i, self.data.extra_specs) self.assertIsNotNone(message) @mock.patch.object(rest.PowerMaxRest, 'add_child_sg_to_parent_sg') @mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg', side_effect=[True, False]) def test_add_child_sg_to_parent_sg(self, mock_is_child, mock_add): for x in range(0, 2): self.mask.add_child_sg_to_parent_sg( self.data.array, self.data.storagegroup_name_i, self.data.parent_sg_i, self.data.extra_specs) self.assertEqual(1, mock_add.call_count) def test_get_parent_sg_from_child(self): with mock.patch.object(self.driver.rest, 'get_storage_group', side_effect=[None, self.data.sg_details[1]]): sg_name = self.mask.get_parent_sg_from_child( self.data.array, self.data.storagegroup_name_i) self.assertIsNone(sg_name) sg_name2 = self.mask.get_parent_sg_from_child( self.data.array, self.data.storagegroup_name_f) self.assertEqual(self.data.parent_sg_f, sg_name2) @mock.patch.object(rest.PowerMaxRest, 'get_element_from_masking_view', return_value='parent_sg') @mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg', return_value=2) def test_get_num_vols_from_mv(self, mock_num, mock_element): num_vols, sg = self.mask._get_num_vols_from_mv( self.data.array, self.data.masking_view_name_f) self.assertEqual(2, num_vols) @mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_default_storage_group') @mock.patch.object(rest.PowerMaxRest, 'delete_storage_group') def test_delete_cascaded(self, mock_delete, mock_add): self.mask._delete_cascaded_storage_groups( self.data.array, self.data.masking_view_name_f, self.data.parent_sg_f, self.data.extra_specs, self.data.device_id, False) self.assertEqual(2, mock_delete.call_count) mock_add.assert_not_called() # Delete legacy masking view, parent sg = child sg mock_delete.reset_mock() self.mask._delete_cascaded_storage_groups( self.data.array, self.data.masking_view_name_f, self.data.masking_view_name_f, self.data.extra_specs, self.data.device_id, True) self.assertEqual(1, mock_delete.call_count) mock_add.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, 'add_volumes_to_storage_group') def test_add_remote_vols_to_volume_group(self, mock_add): self.mask.add_remote_vols_to_volume_group( [self.data.test_volume], self.data.test_rep_group, self.data.rep_extra_specs) mock_add.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, 'add_remote_vols_to_volume_group') @mock.patch.object(masking.PowerMaxMasking, '_check_adding_volume_to_storage_group') @mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type', return_value=True) @mock.patch.object(volume_utils, 'is_group_a_type', side_effect=[False, False, True, True]) def test_return_volume_to_volume_group(self, mock_type, mock_cg, mock_check, mock_add): vol_grp_member = deepcopy(self.data.test_volume) vol_grp_member.group_id = self.data.test_vol_grp_name_id_only vol_grp_member.group = self.data.test_group for x in range(0, 2): self.mask.return_volume_to_volume_group( self.data.array, vol_grp_member, self.data.device_id, self.data.test_volume.name, self.data.extra_specs) mock_add.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, '_return_volume_to_fast_managed_group') def test_pre_multiattach(self, mock_return): mv_dict = self.mask.pre_multiattach( self.data.array, self.data.device_id, self.data.masking_view_dict_multiattach, self.data.extra_specs) mock_return.assert_not_called() self.assertEqual(self.data.storagegroup_name_f, mv_dict[utils.FAST_SG]) with mock.patch.object( self.mask, 'move_volume_between_storage_groups', side_effect=exception.CinderException): self.assertRaises( exception.VolumeBackendAPIException, self.mask.pre_multiattach, self.data.array, self.data.device_id, self.data.masking_view_dict_multiattach, self.data.extra_specs) mock_return.assert_called_once() def test_pre_multiattach_next_gen(self): with mock.patch.object(utils.PowerMaxUtils, 'truncate_string', return_value='DiamondDSS'): self.mask.pre_multiattach( self.data.array, self.data.device_id, self.data.masking_view_dict_multiattach, self.data.extra_specs) utils.PowerMaxUtils.truncate_string.assert_called_once_with( 'DiamondDSS', 10) @mock.patch.object( rest.PowerMaxRest, 'get_storage_group_list', side_effect=[ {'storageGroupId': [tpd.PowerMaxData.no_slo_sg_name]}, {}]) @mock.patch.object(masking.PowerMaxMasking, '_return_volume_to_fast_managed_group') def test_check_return_volume_to_fast_managed_group( self, mock_return, mock_sg): for x in range(0, 2): self.mask.return_volume_to_fast_managed_group( self.data.array, self.data.device_id, self.data.extra_specs) no_slo_specs = deepcopy(self.data.extra_specs) no_slo_specs[utils.SLO] = None self.mask.return_volume_to_fast_managed_group( self.data.array, self.data.device_id, no_slo_specs) mock_return.assert_called_once() @mock.patch.object(masking.PowerMaxMasking, '_move_vol_from_default_sg') @mock.patch.object(masking.PowerMaxMasking, '_clean_up_child_storage_group') @mock.patch.object(masking.PowerMaxMasking, 'add_child_sg_to_parent_sg') @mock.patch.object(masking.PowerMaxMasking, '_get_or_create_storage_group') @mock.patch.object( rest.PowerMaxRest, 'get_storage_groups_from_volume', side_effect=[[tpd.PowerMaxData.no_slo_sg_name], [tpd.PowerMaxData.storagegroup_name_f]]) def test_return_volume_to_fast_managed_group( self, mock_sg, mock_get, mock_add, mock_clean, mock_move): for x in range(0, 2): self.mask._return_volume_to_fast_managed_group( self.data.array, self.data.device_id, self.data.parent_sg_f, self.data.storagegroup_name_f, self.data.no_slo_sg_name, self.data.extra_specs) mock_get.assert_called_once() mock_clean.assert_called_once() @mock.patch.object(rest.PowerMaxRest, 'delete_storage_group') @mock.patch.object(rest.PowerMaxRest, 'remove_child_sg_from_parent_sg') @mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg', side_effect=[False, True]) @mock.patch.object(rest.PowerMaxRest, 'get_num_vols_in_sg', side_effect=[2, 0, 0]) @mock.patch.object(rest.PowerMaxRest, 'get_storage_group', side_effect=[ None, 'child_sg', 'child_sg', 'child_sg']) def test_clean_up_child_storage_group( self, mock_sg, mock_num, mock_child, mock_rm, mock_del): # Storage group not found self.mask._clean_up_child_storage_group( self.data.array, self.data.storagegroup_name_f, self.data.parent_sg_f, self.data.extra_specs) mock_num.assert_not_called() # Storage group not empty self.mask._clean_up_child_storage_group( self.data.array, self.data.storagegroup_name_f, self.data.parent_sg_f, self.data.extra_specs) mock_child.assert_not_called() # Storage group not child self.mask._clean_up_child_storage_group( self.data.array, self.data.storagegroup_name_f, self.data.parent_sg_f, self.data.extra_specs) mock_rm.assert_not_called() # Storage group is child, and empty self.mask._clean_up_child_storage_group( self.data.array, self.data.storagegroup_name_f, self.data.parent_sg_f, self.data.extra_specs) mock_rm.assert_called_once() self.assertEqual(2, mock_del.call_count) @mock.patch.object(utils.PowerMaxUtils, 'verify_tag_list') def test_add_tags_to_storage_group_disabled(self, mock_verify): self.mask._add_tags_to_storage_group( self.data.array, self.data.add_volume_sg_info_dict, self.data.extra_specs) mock_verify.assert_not_called() @mock.patch.object(utils.PowerMaxUtils, 'verify_tag_list') def test_add_tags_to_storage_group_enabled(self, mock_verify): self.mask._add_tags_to_storage_group( self.data.array, self.data.add_volume_sg_info_dict, self.data.extra_specs_tags) mock_verify.assert_called() @mock.patch.object(utils.PowerMaxUtils, 'get_new_tags') def test_add_tags_to_storage_group_existing_tags(self, mock_inter): self.mask._add_tags_to_storage_group( self.data.array, self.data.storage_group_with_tags, self.data.extra_specs_tags) mock_inter.assert_called() @mock.patch.object(rest.PowerMaxRest, 'add_storage_group_tag', side_effect=[exception.VolumeBackendAPIException]) def test_add_tags_to_storage_group_exception(self, mock_except): self.mask._add_tags_to_storage_group( self.data.array, self.data.add_volume_sg_info_dict, self.data.extra_specs_tags) mock_except.assert_called()