From: Xing Yang Date: Thu, 9 Oct 2014 05:26:28 +0000 (-0400) Subject: Use look up service for auto zoning X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=69f9d40e324c76b988d4cb80489618534ca4fcb6;p=openstack-build%2Fcinder-build.git Use look up service for auto zoning The VMAX FC driver didn't use the look up service for auto zoning. Instead it built initiator target map itself. However, that requires the initiator to log into the fabric before zoning in order to find out target WWNs. This patch is to use the look up service to find out valid initiator target WWNS and use that to build initiator target map. With this fix, the initiator is no longer required to log into the fabric ahead of time. Closes-Bug: #1379156 (cherry picked from commit 8707458a98bd1e4059939325659a7290757d972f) Change-Id: I76e1a8a2e7d4230b851b763f7d13dd1489b69364 --- diff --git a/cinder/tests/test_emc_vmax.py b/cinder/tests/test_emc_vmax.py index de26053bb..5bbd40e24 100644 --- a/cinder/tests/test_emc_vmax.py +++ b/cinder/tests/test_emc_vmax.py @@ -128,13 +128,31 @@ class FakeDB(): class EMCVMAXCommonData(): + wwpn1 = "123456789012345" + wwpn2 = "123456789054321" connector = {'ip': '10.0.0.2', 'initiator': 'iqn.1993-08.org.debian: 01: 222', - 'wwpns': ["123456789012345", "123456789054321"], + 'wwpns': [wwpn1, wwpn2], 'wwnns': ["223456789012345", "223456789054321"], 'host': 'fakehost'} + + target_wwns = [wwn[::-1] for wwn in connector['wwpns']] + + fabric_name_prefix = "fakeFabric" + end_point_map = {connector['wwpns'][0]: [target_wwns[0]], + connector['wwpns'][1]: [target_wwns[1]]} + device_map = {} + for wwn in connector['wwpns']: + fabric_name = ''.join([fabric_name_prefix, + wwn[-2:]]) + target_wwn = wwn[::-1] + fabric_map = {'initiator_port_wwn_list': [wwn], + 'target_port_wwn_list': [target_wwn] + } + device_map[fabric_name] = fabric_map + default_storage_group = ( - u'//10.108.246.202/root/emc: SE_DeviceMaskingGroup.InstanceID=' + u'//10.10.10.10/root/emc: SE_DeviceMaskingGroup.InstanceID=' '"SYMMETRIX+000198700440+OS_default_GOLD1_SG"') storage_system = 'SYMMETRIX+000195900551' lunmaskctrl_id =\ @@ -245,6 +263,11 @@ class EMCVMAXCommonData(): diff = {} +class FakeLookupService(): + def get_device_mapping_from_network(self, initiator_wwns, target_wwns): + return EMCVMAXCommonData.device_map + + class FakeEcomConnection(): def __init__(self, *args, **kwargs): @@ -302,10 +325,12 @@ class FakeEcomConnection(): targetendpoints = {} endpoints = [] endpoint = {} - endpoint['Name'] = '1234567890123' + endpoint['Name'] = (EMCVMAXCommonData.end_point_map[ + EMCVMAXCommonData.connector['wwpns'][0]]) endpoints.append(endpoint) endpoint2 = {} - endpoint2['Name'] = '0987654321321' + endpoint2['Name'] = (EMCVMAXCommonData.end_point_map[ + EMCVMAXCommonData.connector['wwpns'][1]]) endpoints.append(endpoint2) targetendpoints['TargetEndpoints'] = endpoints return rc, targetendpoints @@ -428,6 +453,8 @@ class FakeEcomConnection(): result = self._enum_storage_extent() elif ResultClass == 'SE_StorageHardwareID': result = self._enum_storhdwids() + elif ResultClass == 'Symm_FCSCSIProtocolEndpoint': + result = self._enum_fcscsiendpoint() else: result = self._default_assocnames(objectpath) @@ -571,7 +598,6 @@ class FakeEcomConnection(): foundinstance = None else: foundinstance = instance - return foundinstance def _getinstance_lunmask(self): @@ -793,8 +819,6 @@ class FakeEcomConnection(): initatorgroup['DeviceID'] = self.data.initiatorgroup_id initatorgroup['SystemName'] = self.data.storage_system initatorgroup['ElementName'] = self.data.initiatorgroup_name -# initatorgroup.path = initatorgroup -# initatorgroup.path.classname = initatorgroup['CreationClassName'] initatorgroups.append(initatorgroup) return initatorgroups @@ -909,6 +933,13 @@ class FakeEcomConnection(): storhdwids.append(hdwid) return storhdwids + def _enum_fcscsiendpoint(self): + wwns = [] + wwn = {} + wwn['Name'] = "5000090000000000" + wwns.append(wwn) + return wwns + def _default_enum(self): names = [] name = {} @@ -1018,10 +1049,10 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.config_file_1364232 = self.tempdir + '/' + filename text_file = open(self.config_file_1364232, "w") text_file.write("\n\n" - "10.108.246.202\n" + "10.10.10.10\n" "5988\n" - "admin\t\n" - "#1Password\n" + "user\t\n" + "password\n" "OS-PORTGROUP1-PG" "OS-PORTGROUP2-PG" " \n" @@ -1919,6 +1950,8 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): driver = EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() + driver.common.conn = FakeEcomConnection() + driver.zonemanager_lookup_service = FakeLookupService() self.driver = driver def create_fake_config_file_no_fast(self): @@ -2098,30 +2131,44 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) + return_value={'volume_backend_name': 'FCNoFAST', + 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( EMCVMAXMasking, - '_wrap_get_storage_group_from_volume', - return_value=None) - @mock.patch.object( - EMCVMAXCommon, - '_wrap_find_device_number', - return_value={'hostlunid': 1, - 'storagesystem': EMCVMAXCommonData.storage_system}) - def test_map_no_fast_success(self, _mock_volume_type, mock_wrap_group, - mock_wrap_device): - self.driver.initialize_connection(self.data.test_volume, - self.data.connector) + 'get_masking_view_from_storage_group', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + def test_map_lookup_service_no_fast_success( + self, _mock_volume_type, mock_maskingview): + self.data.test_volume['volume_name'] = "vmax-1234567" + common = self.driver.common + common.get_target_wwns_from_masking_view = mock.Mock( + return_value=EMCVMAXCommonData.target_wwns) + lookup_service = self.driver.zonemanager_lookup_service + lookup_service.get_device_mapping_from_network = mock.Mock( + return_value=EMCVMAXCommonData.device_map) + data = self.driver.initialize_connection(self.data.test_volume, + self.data.connector) + common.get_target_wwns_from_masking_view.assert_called_once_with( + EMCVMAXCommonData.storage_system, self.data.test_volume, + EMCVMAXCommonData.connector) + lookup_service.get_device_mapping_from_network.assert_called_once_with( + EMCVMAXCommonData.connector['wwpns'], + EMCVMAXCommonData.target_wwns) + + # Test the lookup service code path. + for init, target in data['data']['initiator_target_map'].items(): + self.assertEqual(init, target[0][::-1]) @mock.patch.object( - EMCVMAXMasking, - '_wrap_get_storage_group_from_volume', - return_value=None) + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST', + 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( EMCVMAXCommon, - '_wrap_find_device_number', - return_value={'storagesystem': EMCVMAXCommonData.storage_system}) - def test_map_no_fast_failed(self, mock_wrap_group, mock_wrap_device): + 'find_device_number', + return_value={'Name': "0001"}) + def test_map_no_fast_failed(self, mock_wrap_group, mock_maskingview): self.assertRaises(exception.VolumeBackendAPIException, self.driver.initialize_connection, self.data.test_volume, @@ -2133,12 +2180,10 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCNoFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_no_fast_success(self, mock_volume_type, - mock_storage_group): - + EMCVMAXMasking, + 'get_masking_view_by_volume', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + def test_detach_no_fast_success(self, mock_volume_type, mock_maskingview): self.driver.terminate_connection(self.data.test_volume, self.data.connector) @@ -2147,16 +2192,12 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXUtils, 'find_storage_system', - return_value={'Name': EMCVMAXCommonData.storage_system}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_no_fast_last_volume_success(self, mock_volume_type, - mock_storage_system, - mock_storage_group): - self.driver.terminate_connection(self.data.test_volume, + EMCVMAXMasking, + 'get_masking_view_by_volume', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + def test_detach_no_fast_last_volume_success( + self, mock_volume_type, mock_mv): + self.driver.terminate_connection(self.data.test_source_volume, self.data.connector) @mock.patch.object( @@ -2319,6 +2360,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): driver = EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() + driver.common.conn = FakeEcomConnection() + driver.zonemanager_lookup_service = None self.driver = driver def create_fake_config_file_fast(self): @@ -2523,30 +2566,35 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST'}) + return_value={'volume_backend_name': 'FCFAST', + 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( EMCVMAXMasking, - '_wrap_get_storage_group_from_volume', - return_value=None) - @mock.patch.object( - EMCVMAXCommon, - '_wrap_find_device_number', - return_value={'hostlunid': 1, - 'storagesystem': EMCVMAXCommonData.storage_system}) - def test_map_fast_success(self, _mock_volume_type, mock_wrap_group, - mock_wrap_device): - self.driver.initialize_connection(self.data.test_volume, - self.data.connector) + 'get_masking_view_from_storage_group', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + def test_map_fast_success(self, _mock_volume_type, mock_maskingview): + self.data.test_volume['volume_name'] = "vmax-1234567" + common = self.driver.common + common.get_target_wwns = mock.Mock( + return_value=EMCVMAXCommonData.target_wwns) + data = self.driver.initialize_connection( + self.data.test_volume, self.data.connector) + # Test the no lookup service, pre-zoned case. + common.get_target_wwns.assert_called_once_with( + EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) + for init, target in data['data']['initiator_target_map'].items(): + self.assertIn(init[::-1], target) @mock.patch.object( - EMCVMAXMasking, - '_wrap_get_storage_group_from_volume', - return_value=None) + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST', + 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( EMCVMAXCommon, - '_wrap_find_device_number', - return_value={'storagesystem': EMCVMAXCommonData.storage_system}) - def test_map_fast_failed(self, mock_wrap_group, mock_wrap_device): + 'find_device_number', + return_value={'Name': "0001"}) + def test_map_fast_failed(self, mock_wrap_group, mock_maskingview): self.assertRaises(exception.VolumeBackendAPIException, self.driver.initialize_connection, self.data.test_volume, @@ -2558,31 +2606,19 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_fast_success(self, mock_volume_type, - mock_storage_group): - - self.driver.terminate_connection(self.data.test_volume, - self.data.connector) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST'}) - @mock.patch.object( - EMCVMAXUtils, 'find_storage_system', - return_value={'Name': EMCVMAXCommonData.storage_system}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_fast_last_volume_success( - self, mock_volume_type, - mock_storage_system, mock_storage_group): - self.driver.terminate_connection(self.data.test_volume, - self.data.connector) + EMCVMAXMasking, + 'get_masking_view_by_volume', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + def test_detach_fast_success(self, mock_volume_type, mock_maskingview): + common = self.driver.common + common.get_target_wwns = mock.Mock( + return_value=EMCVMAXCommonData.target_wwns) + data = self.driver.terminate_connection(self.data.test_volume, + self.data.connector) + common.get_target_wwns.assert_called_once_with( + EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) + + self.assertEqual(0, len(data['data'])) @mock.patch.object( volume_types, diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 20a0f6ba4..0f5c908f7 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -1264,6 +1264,8 @@ class EMCVMAXCommon(object): volumename = volume['name'] loc = volume['provider_location'] + if self.conn is None: + self.conn = self._get_ecom_connection() if isinstance(loc, six.string_types): name = eval(loc) @@ -2185,3 +2187,54 @@ class EMCVMAXCommon(object): 'connector': connector}) return numVolumesMapped + + def get_target_wwns_from_masking_view( + self, storageSystem, volume, connector): + """Find target WWNs via the masking view. + + :param storageSystem: the storage system name + :param volume: volume to be attached + :param connector: the connector dict + :returns: targetWwns, the target WWN list + """ + targetWwns = [] + mvInstanceName = self.get_masking_view_by_volume(volume) + targetWwns = self.masking.get_target_wwns(self.conn, mvInstanceName) + LOG.info("Target wwns in masking view %(maskingView)s: %(targetWwns)s" + % {'maskingView': mvInstanceName, + 'targetWwns': str(targetWwns)}) + return targetWwns + + def get_port_group_from_masking_view(self, maskingViewInstanceName): + """Find port group that is part of a masking view. + + :param maskingViewInstanceName: the owning masking view + :returns: port group instance name + """ + return self.masking.get_port_group_from_masking_view( + self.conn, maskingViewInstanceName) + + def get_masking_view_by_volume(self, volume): + """Given volume, retrieve the masking view instance name + + :param volume: the volume + :param mvInstanceName: masking view instance name + :returns maskingviewInstanceName + """ + LOG.debug("Finding Masking View for volume %(volume)s" + % {'volume': volume}) + volumeInstance = self._find_lun(volume) + return self.masking.get_masking_view_by_volume( + self.conn, volumeInstance) + + def get_masking_views_by_port_group(self, portGroupInstanceName): + """Given port group, retrieve the masking view instance name + + :param : the volume + :param mvInstanceName: masking view instance name + :returns: maksingViewInstanceNames + """ + LOG.debug("Finding Masking Views for port group %(pg)s" + % {'pg': portGroupInstanceName}) + return self.masking.get_masking_views_by_port_group( + self.conn, portGroupInstanceName) diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index b5aba2851..abc6f445b 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -20,7 +20,6 @@ from cinder.volume import driver from cinder.volume.drivers.emc import emc_vmax_common from cinder.zonemanager import utils as fczm_utils - LOG = logging.getLogger(__name__) @@ -42,6 +41,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): self.common = emc_vmax_common.EMCVMAXCommon( 'FC', configuration=self.configuration) + self.zonemanager_lookup_service = fczm_utils.create_lookup_service() def check_for_setup_error(self): pass @@ -154,7 +154,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): device_number = device_info['hostlunid'] storage_system = device_info['storagesystem'] target_wwns, init_targ_map = self._build_initiator_target_map( - storage_system, connector) + storage_system, volume, connector) data = {'driver_volume_type': 'fibre_channel', 'data': {'target_lun': device_number, @@ -162,7 +162,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 'target_wwn': target_wwns, 'initiator_target_map': init_targ_map}} - LOG.debug("Return FC data: %(data)s." + LOG.debug("Return FC data for zone addition: %(data)s." % {'data': data}) return data @@ -179,40 +179,71 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): :returns: data - the target_wwns and initiator_target_map if the zone is to be removed, otherwise empty """ - self.common.terminate_connection(volume, connector) - loc = volume['provider_location'] name = eval(loc) storage_system = name['keybindings']['SystemName'] + LOG.info("Start FC detach process for volume: %(volume)s" + % {'volume': volume['name']}) + + target_wwns, init_targ_map = self._build_initiator_target_map( + storage_system, volume, connector) + + mvInstanceName = self.common.get_masking_view_by_volume(volume) + portGroupInstanceName = self.common.get_port_group_from_masking_view( + mvInstanceName) + + LOG.info("Found port group: %(portGroup)s " + "in masking view %(maskingView)s" + % {'portGroup': portGroupInstanceName, + 'maskingView': mvInstanceName}) - numVolumes = self.common.get_num_volumes_mapped(volume, connector) - if numVolumes > 0: + self.common.terminate_connection(volume, connector) + + LOG.info("Looking for masking views still associated with" + "Port Group %s" % portGroupInstanceName) + mvInstances = self.common.get_masking_views_by_port_group( + portGroupInstanceName) + if len(mvInstances) > 0: + LOG.debug("Found %(numViews)lu maskingviews." + % {'numViews': len(mvInstances)}) data = {'driver_volume_type': 'fibre_channel', 'data': {}} - else: - target_wwns, init_targ_map = self._build_initiator_target_map( - storage_system, connector) + else: # no views found + LOG.debug("No Masking Views were found. Deleting zone.") data = {'driver_volume_type': 'fibre_channel', 'data': {'target_wwn': target_wwns, 'initiator_target_map': init_targ_map}} - LOG.debug("Return FC data: %(data)s." + LOG.debug("Return FC data for zone removal: %(data)s." % {'data': data}) return data - def _build_initiator_target_map(self, storage_system, connector): + def _build_initiator_target_map(self, storage_system, volume, connector): """Build the target_wwns and the initiator target map.""" - - target_wwns = self.common.get_target_wwns(storage_system, connector) + target_wwns = [] + init_targ_map = {} initiator_wwns = connector['wwpns'] - init_targ_map = {} - for initiator in initiator_wwns: - init_targ_map[initiator] = target_wwns - - return target_wwns, init_targ_map + if self.zonemanager_lookup_service: + fc_targets = self.common.get_target_wwns_from_masking_view( + storage_system, volume, connector) + mapping = ( + self.zonemanager_lookup_service. + get_device_mapping_from_network(initiator_wwns, fc_targets)) + for entry in mapping: + map_d = mapping[entry] + target_wwns.extend(map_d['target_port_wwn_list']) + for initiator in map_d['initiator_port_wwn_list']: + init_targ_map[initiator] = map_d['target_port_wwn_list'] + else: # no lookup service, pre-zoned case + target_wwns = self.common.get_target_wwns(storage_system, + connector) + for initiator in initiator_wwns: + init_targ_map[initiator] = target_wwns + + return list(set(target_wwns)), init_targ_map def extend_volume(self, volume, new_size): """Extend an existing volume.""" diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py index de7965855..2ff95a560 100644 --- a/cinder/volume/drivers/emc/emc_vmax_masking.py +++ b/cinder/volume/drivers/emc/emc_vmax_masking.py @@ -1396,3 +1396,64 @@ class EMCVMAXMasking(object): LOG.debug( "end: number of volumes in default storage group: %(numVol)d" % {'numVol': len(volumeInstanceNames)}) + + def get_target_wwns(self, conn, mvInstanceName): + """Get the DA ports' wwns. + + :param conn: the ecom connection + :param mvInstanceName: masking view instance name + """ + targetWwns = [] + targetPortInstanceNames = conn.AssociatorNames( + mvInstanceName, + ResultClass='Symm_FCSCSIProtocolEndpoint') + numberOfPorts = len(targetPortInstanceNames) + if numberOfPorts <= 0: + LOG.warn("No target ports found in " + "masking view %(maskingView)s" + % {'numPorts': len(targetPortInstanceNames), + 'maskingView': mvInstanceName}) + for targetPortInstanceName in targetPortInstanceNames: + targetWwns.append(targetPortInstanceName['Name']) + return targetWwns + + def get_masking_view_by_volume(self, conn, volumeInstance): + """Given volume, retrieve the masking view instance name. + + :param volume: the volume instance + :param mvInstanceName: masking view instance name + """ + sgInstanceName = self.get_associated_masking_group_from_device( + conn, volumeInstance.path) + mvInstanceName = self.get_masking_view_from_storage_group( + conn, sgInstanceName) + LOG.debug("Found Masking View %(mv)s: " % {'mv': mvInstanceName}) + return mvInstanceName + + def get_masking_views_by_port_group(self, conn, portGroupInstanceName): + """Given port group, retrieve the masking view instance name. + + :param : the volume + :param mvInstanceName: masking view instance name + :returns: maksingViewInstanceNames + """ + mvInstanceNames = conn.AssociatorNames( + portGroupInstanceName, ResultClass='Symm_LunMaskingView') + return mvInstanceNames + + def get_port_group_from_masking_view(self, conn, maskingViewInstanceName): + """Get the port group in a masking view. + + :param maskingViewInstanceName: masking view instance name + :returns: portGroupInstanceName + """ + portGroupInstanceNames = conn.AssociatorNames( + maskingViewInstanceName, ResultClass='SE_TargetMaskingGroup') + if len(portGroupInstanceNames) > 0: + LOG.debug("Found port group %(pg)s in masking view %(mv)s" + % {'pg': portGroupInstanceNames[0], + 'mv': maskingViewInstanceName}) + return portGroupInstanceNames[0] + else: + LOG.warn("No port group found in masking view %(mv)s" + % {'mv': maskingViewInstanceName})