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.

Change-Id: I76e1a8a2e7d4230b851b763f7d13dd1489b69364
Closes-Bug: #1379156
This commit is contained in:
Xing Yang 2014-10-09 01:26:28 -04:00
parent 9a13288cd2
commit 8707458a98
4 changed files with 285 additions and 104 deletions

View File

@ -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("<?xml version='1.0' encoding='UTF-8'?>\n<EMC>\n"
"<EcomServerIp>10.108.246.202</EcomServerIp>\n"
"<EcomServerIp>10.10.10.10</EcomServerIp>\n"
"<EcomServerPort>5988</EcomServerPort>\n"
"<EcomUserName>admin\t</EcomUserName>\n"
"<EcomPassword>#1Password</EcomPassword>\n"
"<EcomUserName>user\t</EcomUserName>\n"
"<EcomPassword>password</EcomPassword>\n"
"<PortGroups><PortGroup>OS-PORTGROUP1-PG"
"</PortGroup><PortGroup>OS-PORTGROUP2-PG"
" </PortGroup>\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):
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.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)
self.assertEqual(0, len(data['data']))
@mock.patch.object(
volume_types,

View File

@ -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)

View File

@ -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']})
numVolumes = self.common.get_num_volumes_mapped(volume, connector)
if numVolumes > 0:
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})
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
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 target_wwns, init_targ_map
return list(set(target_wwns)), init_targ_map
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""

View File

@ -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})