# Copyright 2015 Violin Memory, Inc. # 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. """ Tests for Violin Memory 7000 Series All-Flash Array Fibrechannel Driver """ import mock from cinder import exception from cinder import test from cinder.tests.unit import fake_vmem_client as vmemclient from cinder.volume import configuration as conf from cinder.volume.drivers.violin import v7000_common from cinder.volume.drivers.violin import v7000_fcp VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba" VOLUME = { "name": "volume-" + VOLUME_ID, "id": VOLUME_ID, "display_name": "fake_volume", "size": 2, "host": "myhost", "volume_type": None, "volume_type_id": None, } SNAPSHOT_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbb" SNAPSHOT = { "name": "snapshot-" + SNAPSHOT_ID, "id": SNAPSHOT_ID, "volume_id": VOLUME_ID, "volume_name": "volume-" + VOLUME_ID, "volume_size": 2, "display_name": "fake_snapshot", "volume": VOLUME, } SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc" SRC_VOL = { "name": "volume-" + SRC_VOL_ID, "id": SRC_VOL_ID, "display_name": "fake_src_vol", "size": 2, "host": "myhost", "volume_type": None, "volume_type_id": None, } INITIATOR_IQN = "iqn.1111-22.org.debian:11:222" CONNECTOR = { "initiator": INITIATOR_IQN, "host": "irrelevant", 'wwpns': ['50014380186b3f65', '50014380186b3f67'], } FC_TARGET_WWPNS = [ '31000024ff45fb22', '21000024ff45fb23', '51000024ff45f1be', '41000024ff45f1bf' ] FC_INITIATOR_WWPNS = [ '50014380186b3f65', '50014380186b3f67' ] FC_FABRIC_MAP = { 'fabricA': {'target_port_wwn_list': [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]], 'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[0]]}, 'fabricB': {'target_port_wwn_list': [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]], 'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[1]]} } FC_INITIATOR_TARGET_MAP = { FC_INITIATOR_WWPNS[0]: [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]], FC_INITIATOR_WWPNS[1]: [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]] } PHY_DEVICES_RESPONSE = { 'data': {'physical_devices': [{'availsize': 1099504287744, 'availsize_mb': 524284, 'category': 'Virtual Device', 'connection_type': 'block', 'firmware': 'v1.0', 'guid': '3cc4d6dd-166d-77d2-4967-00005463f597', 'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN01 v1.0', 'is_foreign': True, 'name': 'BKSC:OTHDISK-MFCN01.000', 'object_id': '84b834fb-1f4d-5d3b-b7ae-5796f9868151', 'owner': 'example.com', 'pool': None, 'product': 'OTHDISK-MFCN01', 'scsi_address': {'adapter': '98', 'channel': '0', 'id': '0', 'lun': '0', 'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'}, 'size': 1099504287744, 'size_mb': 1048569, 'type': 'Direct-Access', 'usedsize': 0, 'usedsize_mb': 0, 'vendor': 'BKSC', 'wwid': 'BKSC OTHDISK-MFCN01 v1.0-0-0-00'}, {'availsize': 1099504287744, 'availsize_mb': 524284, 'category': 'Virtual Device', 'connection_type': 'block', 'firmware': 'v1.0', 'guid': '283b2694-192b-4745-6768-00005463f673', 'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN08 v1.0', 'is_foreign': False, 'name': 'BKSC:OTHDISK-MFCN08.000', 'object_id': '8555b888-bf43-5083-a433-f0c7b0282370', 'owner': 'example.com', 'pool': {'name': 'mga-pool', 'object_id': '0818d3de-4437-535f-9cac-cc100a2c9313'}, 'product': 'OTHDISK-MFCN08', 'scsi_address': {'adapter': '98', 'channel': '0', 'id': '11', 'lun': '0', 'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'}, 'size': 1099504287744, 'size_mb': 1048569, 'type': 'Direct-Access', 'usedsize': 0, 'usedsize_mb': 0, 'vendor': 'BKSC', 'wwid': 'BKSC OTHDISK-MFCN08 v1.0-0-0-00'}, {'availsize': 1099504287744, 'availsize_mb': 1048569, 'category': 'Virtual Device', 'connection_type': 'block', 'firmware': 'v1.0', 'guid': '7f47db19-019c-707d-0df1-00005463f949', 'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN09 v1.0', 'is_foreign': False, 'name': 'BKSC:OTHDISK-MFCN09.000', 'object_id': '62a98898-f8b8-5837-af2b-764f5a72e291', 'owner': 'a.b.c.d', 'pool': {'name': 'mga-pool', 'object_id': '0818d3de-4437-535f-9cac-cc100a2c9313'}, 'product': 'OTHDISK-MFCN09', 'scsi_address': {'adapter': '98', 'channel': '0', 'id': '12', 'lun': '0', 'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'}, 'size': 1099504287744, 'size_mb': 524284, 'type': 'Direct-Access', 'usedsize': 0, 'usedsize_mb': 0, 'vendor': 'BKSC', 'wwid': 'BKSC OTHDISK-MFCN09 v1.0-0-0-00'}], 'total_physical_devices': 3}, 'msg': 'Successful', 'success': True } # The FC_INFO dict returned by the backend is keyed on # object_id of the FC adapter and the values are the # wwmns FC_INFO = { '1a3cdb6a-383d-5ba6-a50b-4ba598074510': ['2100001b9745e25e'], '4a6bc10a-5547-5cc0-94f2-76222a8f8dff': ['2100001b9745e230'], 'b21bfff5-d89e-51ff-9920-d990a061d722': ['2100001b9745e25f'], 'b508cc6b-f78a-51f9-81cf-47c1aaf53dd1': ['2100001b9745e231'] } CLIENT_INFO = { 'FCPolicy': {'AS400enabled': False, 'VSAenabled': False, 'initiatorWWPNList': ['50-01-43-80-18-6b-3f-66', '50-01-43-80-18-6b-3f-64']}, 'FibreChannelDevices': [{'access': 'ReadWrite', 'id': 'v0000004', 'initiatorWWPN': '*', 'lun': '8', 'name': 'abcdabcd-1234-abcd-1234-abcdeffedcba', 'sizeMB': 10240, 'targetWWPN': '*', 'type': 'SAN'}] } CLIENT_INFO1 = { 'FCPolicy': {'AS400enabled': False, 'VSAenabled': False, 'initiatorWWPNList': ['50-01-43-80-18-6b-3f-66', '50-01-43-80-18-6b-3f-64']}, 'FibreChannelDevices': [] } class V7000FCPDriverTestCase(test.TestCase): """Test cases for VMEM FCP driver.""" def setUp(self): super(V7000FCPDriverTestCase, self).setUp() self.conf = self.setup_configuration() self.driver = v7000_fcp.V7000FCPDriver(configuration=self.conf) self.driver.common.container = 'myContainer' self.driver.device_id = 'ata-VIOLIN_MEMORY_ARRAY_23109R00000022' self.driver.gateway_fc_wwns = FC_TARGET_WWPNS self.stats = {} self.driver.set_initialized() def tearDown(self): super(V7000FCPDriverTestCase, self).tearDown() def setup_configuration(self): config = mock.Mock(spec=conf.Configuration) config.volume_backend_name = 'v7000_fcp' config.san_ip = '8.8.8.8' config.san_login = 'admin' config.san_password = '' config.san_thin_provision = False config.san_is_local = False config.request_timeout = 300 config.container = 'myContainer' return config def setup_mock_concerto(self, m_conf=None): """Create a fake Concerto communication object.""" _m_concerto = mock.Mock(name='Concerto', version='1.1.1', spec=vmemclient.mock_client_conf) if m_conf: _m_concerto.configure_mock(**m_conf) return _m_concerto @mock.patch.object(v7000_common.V7000Common, 'check_for_setup_error') def test_check_for_setup_error(self, m_setup_func): """No setup errors are found.""" result = self.driver.check_for_setup_error() m_setup_func.assert_called_with() self.assertIsNone(result) @mock.patch.object(v7000_common.V7000Common, 'check_for_setup_error') def test_check_for_setup_error_no_wwn_config(self, m_setup_func): """No wwns were found during setup.""" self.driver.gateway_fc_wwns = [] failure = exception.ViolinInvalidBackendConfig self.assertRaises(failure, self.driver.check_for_setup_error) def test_create_volume(self): """Volume created successfully.""" self.driver.common._create_lun = mock.Mock() result = self.driver.create_volume(VOLUME) self.driver.common._create_lun.assert_called_with(VOLUME) self.assertIsNone(result) def test_create_volume_from_snapshot(self): self.driver.common._create_volume_from_snapshot = mock.Mock() result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT) self.driver.common._create_volume_from_snapshot.assert_called_with( SNAPSHOT, VOLUME) self.assertIsNone(result) def test_create_cloned_volume(self): self.driver.common._create_lun_from_lun = mock.Mock() result = self.driver.create_cloned_volume(VOLUME, SRC_VOL) self.driver.common._create_lun_from_lun.assert_called_with( SRC_VOL, VOLUME) self.assertIsNone(result) def test_delete_volume(self): """Volume deleted successfully.""" self.driver.common._delete_lun = mock.Mock() result = self.driver.delete_volume(VOLUME) self.driver.common._delete_lun.assert_called_with(VOLUME) self.assertIsNone(result) def test_extend_volume(self): """Volume extended successfully.""" new_size = 10 self.driver.common._extend_lun = mock.Mock() result = self.driver.extend_volume(VOLUME, new_size) self.driver.common._extend_lun.assert_called_with(VOLUME, new_size) self.assertIsNone(result) def test_create_snapshot(self): self.driver.common._create_lun_snapshot = mock.Mock() result = self.driver.create_snapshot(SNAPSHOT) self.driver.common._create_lun_snapshot.assert_called_with(SNAPSHOT) self.assertIsNone(result) def test_delete_snapshot(self): self.driver.common._delete_lun_snapshot = mock.Mock() result = self.driver.delete_snapshot(SNAPSHOT) self.driver.common._delete_lun_snapshot.assert_called_with(SNAPSHOT) self.assertIsNone(result) def test_get_volume_stats(self): self.driver._update_volume_stats = mock.Mock() self.driver._update_volume_stats() result = self.driver.get_volume_stats(True) self.driver._update_volume_stats.assert_called_with() self.assertEqual(self.driver.stats, result) @mock.patch('socket.gethostbyaddr') def test_update_volume_stats(self, mock_gethost): """Test Update Volume Stats. Makes a mock query to the backend to collect stats on all physical devices. """ def gethostbyaddr(addr): if addr == '8.8.8.8' or addr == 'example.com': return ('example.com', [], ['8.8.8.8']) else: return ('a.b.c.d', [], addr) mock_gethost.side_effect = gethostbyaddr backend_name = self.conf.volume_backend_name vendor_name = "Violin Memory, Inc." tot_gb = 2046 free_gb = 1022 phy_devices = "/batch/physicalresource/physicaldevice" conf = { 'basic.get.side_effect': [PHY_DEVICES_RESPONSE, ], } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) result = self.driver._update_volume_stats() calls = [mock.call(phy_devices)] self.driver.common.vmem_mg.basic.get.assert_has_calls(calls) self.assertEqual(tot_gb, self.driver.stats['total_capacity_gb']) self.assertEqual(free_gb, self.driver.stats['free_capacity_gb']) self.assertEqual(backend_name, self.driver.stats['volume_backend_name']) self.assertEqual(vendor_name, self.driver.stats['vendor_name']) self.assertIsNone(result) def test_get_active_fc_targets(self): """Test Get Active FC Targets. Makes a mock query to the backend to collect all the physical adapters and extract the WWNs. """ conf = { 'adapter.get_fc_info.return_value': FC_INFO, } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) result = self.driver._get_active_fc_targets() self.assertEqual({'2100001b9745e230', '2100001b9745e25f', '2100001b9745e231', '2100001b9745e25e'}, set(result)) def test_initialize_connection(self): lun_id = 1 target_wwns = self.driver.gateway_fc_wwns init_targ_map = {} conf = { 'client.create_client.return_value': None, } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) self.driver._export_lun = mock.Mock(return_value=lun_id) self.driver._build_initiator_target_map = mock.Mock( return_value=(target_wwns, init_targ_map)) props = self.driver.initialize_connection(VOLUME, CONNECTOR) self.driver.common.vmem_mg.client.create_client.assert_called_with( name=CONNECTOR['host'], proto='FC', fc_wwns=CONNECTOR['wwpns']) self.driver._export_lun.assert_called_with(VOLUME, CONNECTOR) self.driver._build_initiator_target_map.assert_called_with( CONNECTOR) self.assertEqual("fibre_channel", props['driver_volume_type']) self.assertTrue(props['data']['target_discovered']) self.assertEqual(self.driver.gateway_fc_wwns, props['data']['target_wwn']) self.assertEqual(lun_id, props['data']['target_lun']) def test_terminate_connection(self): target_wwns = self.driver.gateway_fc_wwns init_targ_map = {} self.driver.common.vmem_mg = self.setup_mock_concerto() self.driver._unexport_lun = mock.Mock() self.driver._is_initiator_connected_to_array = mock.Mock( return_value=False) self.driver._build_initiator_target_map = mock.Mock( return_value=(target_wwns, init_targ_map)) props = self.driver.terminate_connection(VOLUME, CONNECTOR) self.driver._unexport_lun.assert_called_with(VOLUME, CONNECTOR) self.driver._is_initiator_connected_to_array.assert_called_with( CONNECTOR) self.driver._build_initiator_target_map.assert_called_with( CONNECTOR) self.assertEqual("fibre_channel", props['driver_volume_type']) self.assertEqual(target_wwns, props['data']['target_wwn']) self.assertEqual(init_targ_map, props['data']['initiator_target_map']) def test_export_lun(self): lun_id = '1' response = {'success': True, 'msg': 'Assign SAN client successfully'} conf = { 'client.get_client_info.return_value': CLIENT_INFO, } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) self.driver.common._send_cmd_and_verify = mock.Mock( return_value=response) self.driver._get_lun_id = mock.Mock(return_value=lun_id) result = self.driver._export_lun(VOLUME, CONNECTOR) self.driver.common._send_cmd_and_verify.assert_called_with( self.driver.common.vmem_mg.lun.assign_lun_to_client, self.driver._is_lun_id_ready, 'Assign SAN client successfully', [VOLUME['id'], CONNECTOR['host'], "ReadWrite"], [VOLUME['id'], CONNECTOR['host']]) self.driver._get_lun_id.assert_called_with( VOLUME['id'], CONNECTOR['host']) self.assertEqual(lun_id, result) def test_export_lun_fails_with_exception(self): lun_id = '1' response = {'status': False, 'msg': 'Generic error'} failure = exception.ViolinBackendErr self.driver.common.vmem_mg = self.setup_mock_concerto() self.driver.common._send_cmd_and_verify = mock.Mock( side_effect=exception.ViolinBackendErr(response['msg'])) self.driver._get_lun_id = mock.Mock(return_value=lun_id) self.assertRaises(failure, self.driver._export_lun, VOLUME, CONNECTOR) def test_unexport_lun(self): response = {'success': True, 'msg': 'Unassign SAN client successfully'} self.driver.common.vmem_mg = self.setup_mock_concerto() self.driver.common._send_cmd = mock.Mock( return_value=response) result = self.driver._unexport_lun(VOLUME, CONNECTOR) self.driver.common._send_cmd.assert_called_with( self.driver.common.vmem_mg.lun.unassign_client_lun, "Unassign SAN client successfully", VOLUME['id'], CONNECTOR['host'], True) self.assertIsNone(result) def test_get_lun_id(self): conf = { 'client.get_client_info.return_value': CLIENT_INFO, } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) result = self.driver._get_lun_id(VOLUME['id'], CONNECTOR['host']) self.assertEqual(8, result) def test_is_lun_id_ready(self): lun_id = '1' self.driver.common.vmem_mg = self.setup_mock_concerto() self.driver._get_lun_id = mock.Mock(return_value=lun_id) result = self.driver._is_lun_id_ready( VOLUME['id'], CONNECTOR['host']) self.assertTrue(result) def test_build_initiator_target_map(self): """Successfully build a map when zoning is enabled.""" expected_targ_wwns = FC_TARGET_WWPNS self.driver.lookup_service = mock.Mock() (self.driver.lookup_service.get_device_mapping_from_network. return_value) = FC_FABRIC_MAP result = self.driver._build_initiator_target_map(CONNECTOR) (targ_wwns, init_targ_map) = result (self.driver.lookup_service.get_device_mapping_from_network. assert_called_with(CONNECTOR['wwpns'], self.driver.gateway_fc_wwns)) self.assertEqual(set(expected_targ_wwns), set(targ_wwns)) i = FC_INITIATOR_WWPNS[0] self.assertIn(FC_TARGET_WWPNS[0], init_targ_map[i]) self.assertIn(FC_TARGET_WWPNS[1], init_targ_map[i]) self.assertEqual(2, len(init_targ_map[i])) i = FC_INITIATOR_WWPNS[1] self.assertIn(FC_TARGET_WWPNS[2], init_targ_map[i]) self.assertIn(FC_TARGET_WWPNS[3], init_targ_map[i]) self.assertEqual(2, len(init_targ_map[i])) self.assertEqual(2, len(init_targ_map)) def test_build_initiator_target_map_no_lookup_service(self): """Successfully build a map when zoning is disabled.""" expected_targ_wwns = FC_TARGET_WWPNS expected_init_targ_map = { CONNECTOR['wwpns'][0]: FC_TARGET_WWPNS, CONNECTOR['wwpns'][1]: FC_TARGET_WWPNS } self.driver.lookup_service = None targ_wwns, init_targ_map = self.driver._build_initiator_target_map( CONNECTOR) self.assertEqual(expected_targ_wwns, targ_wwns) self.assertEqual(expected_init_targ_map, init_targ_map) def test_is_initiator_connected_to_array(self): """Successfully finds an initiator with remaining active session.""" conf = { 'client.get_client_info.return_value': CLIENT_INFO, } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) self.assertTrue(self.driver._is_initiator_connected_to_array( CONNECTOR)) self.driver.common.vmem_mg.client.get_client_info.assert_called_with( CONNECTOR['host']) def test_is_initiator_connected_to_array_empty_response(self): """Successfully finds no initiators with remaining active sessions.""" conf = { 'client.get_client_info.return_value': CLIENT_INFO1 } self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf) self.assertFalse(self.driver._is_initiator_connected_to_array( CONNECTOR))