Merge "VMAX driver - failure in initiator group cleanup" into stable/ocata

This commit is contained in:
Jenkins 2017-06-25 19:31:14 +00:00 committed by Gerrit Code Review
commit 73d35c3b28
3 changed files with 94 additions and 81 deletions

View File

@ -258,6 +258,7 @@ class VMAXCommonData(object):
'wwpns': [wwpn1, wwpn2], 'wwpns': [wwpn1, wwpn2],
'wwnns': ["223456789012345", "223456789054321"], 'wwnns': ["223456789012345", "223456789054321"],
'host': 'fakehost'} 'host': 'fakehost'}
short_host_name = 'fakehost'
target_wwns = [wwn[::-1] for wwn in connector['wwpns']] target_wwns = [wwn[::-1] for wwn in connector['wwpns']]
@ -2823,7 +2824,6 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
def test_last_volume_delete_initiator_group_exception(self): def test_last_volume_delete_initiator_group_exception(self):
extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} extraSpecs = {'volume_backend_name': 'ISCSINoFAST'}
conn = self.fake_ecom_connection() conn = self.fake_ecom_connection()
host = self.data.lunmaskctrl_name.split("-")[1]
controllerConfigService = ( controllerConfigService = (
self.driver.utils.find_controller_configuration_service( self.driver.utils.find_controller_configuration_service(
conn, self.data.storage_system)) conn, self.data.storage_system))
@ -2844,14 +2844,13 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
exception.VolumeBackendAPIException, exception.VolumeBackendAPIException,
self.driver.common.masking._last_volume_delete_initiator_group, self.driver.common.masking._last_volume_delete_initiator_group,
conn, controllerConfigService, initiatorGroupInstanceName, conn, controllerConfigService, initiatorGroupInstanceName,
extraSpecs, host) extraSpecs, self.data.short_host_name)
# Bug 1504192 - if the last volume is being unmapped and the masking view # Bug 1504192 - if the last volume is being unmapped and the masking view
# goes away, cleanup the initiators and associated initiator group. # goes away, cleanup the initiators and associated initiator group.
def test_last_volume_delete_initiator_group(self): def test_last_volume_delete_initiator_group(self):
extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} extraSpecs = {'volume_backend_name': 'ISCSINoFAST'}
conn = self.fake_ecom_connection() conn = self.fake_ecom_connection()
host = self.data.lunmaskctrl_name.split("-")[1]
controllerConfigService = ( controllerConfigService = (
self.driver.utils.find_controller_configuration_service( self.driver.utils.find_controller_configuration_service(
conn, self.data.storage_system)) conn, self.data.storage_system))
@ -2866,14 +2865,14 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
# initiator group will not be deleted. # initiator group will not be deleted.
self.driver.common.masking._last_volume_delete_initiator_group( self.driver.common.masking._last_volume_delete_initiator_group(
conn, controllerConfigService, initiatorGroupInstanceName, conn, controllerConfigService, initiatorGroupInstanceName,
extraSpecs, host) extraSpecs, self.data.short_host_name)
# Path 2: initiator group name is not the default name so the # Path 2: initiator group name is not the default name so the
# initiator group will not be deleted. # initiator group will not be deleted.
initGroup2 = initiatorGroupInstanceName initGroup2 = initiatorGroupInstanceName
initGroup2['ElementName'] = "different-name-ig" initGroup2['ElementName'] = "different-name-ig"
self.driver.common.masking._last_volume_delete_initiator_group( self.driver.common.masking._last_volume_delete_initiator_group(
conn, controllerConfigService, initGroup2, conn, controllerConfigService, initGroup2,
extraSpecs, host) extraSpecs, self.data.short_host_name)
# Path 3: No Masking view and IG is the default IG, so initiators # Path 3: No Masking view and IG is the default IG, so initiators
# associated with the Initiator group and the initiator group will # associated with the Initiator group and the initiator group will
# be deleted. # be deleted.
@ -2883,7 +2882,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
mock.Mock(return_value=True)) mock.Mock(return_value=True))
self.driver.common.masking._last_volume_delete_initiator_group( self.driver.common.masking._last_volume_delete_initiator_group(
conn, controllerConfigService, initiatorGroupInstanceName, conn, controllerConfigService, initiatorGroupInstanceName,
extraSpecs, host) extraSpecs, self.data.short_host_name)
job = { job = {
'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}} 'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}}
conn.InvokeMethod = mock.Mock(return_value=(4096, job)) conn.InvokeMethod = mock.Mock(return_value=(4096, job))
@ -2893,7 +2892,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
# to complete. # to complete.
self.driver.common.masking._last_volume_delete_initiator_group( self.driver.common.masking._last_volume_delete_initiator_group(
conn, controllerConfigService, initiatorGroupInstanceName, conn, controllerConfigService, initiatorGroupInstanceName,
extraSpecs, host) extraSpecs, self.data.short_host_name)
# Tests removal of last volume in a storage group V2 # Tests removal of last volume in a storage group V2
def test_remove_and_reset_members(self): def test_remove_and_reset_members(self):
@ -6189,9 +6188,10 @@ class EMCV3DriverTestCase(test.TestCase):
conn, controllerConfigService, storagegroup, conn, controllerConfigService, storagegroup,
storagegroup['ElementName'], vol, vol['name'], extraSpecs)) storagegroup['ElementName'], vol, vol['name'], extraSpecs))
def test_last_vol_in_SG_no_MV_fail(self): @mock.patch.object(utils.VMAXUtils,
self.driver.common.masking.utils.get_existing_instance = ( 'get_existing_instance',
mock.Mock(return_value='value')) return_value='value')
def test_last_vol_in_SG_no_MV_fail(self, mock_ins):
conn = self.fake_ecom_connection() conn = self.fake_ecom_connection()
controllerConfigService = ( controllerConfigService = (
self.driver.common.utils.find_controller_configuration_service( self.driver.common.utils.find_controller_configuration_service(
@ -6206,7 +6206,7 @@ class EMCV3DriverTestCase(test.TestCase):
self.driver.common.masking._last_vol_in_SG, self.driver.common.masking._last_vol_in_SG,
conn, controllerConfigService, conn, controllerConfigService,
storagegroup, storagegroup['ElementName'], vol, storagegroup, storagegroup['ElementName'], vol,
vol['name'], extraSpecs) vol['name'], extraSpecs, self.data.connector)
@mock.patch.object( @mock.patch.object(
utils.VMAXUtils, utils.VMAXUtils,
@ -7910,12 +7910,12 @@ class VMAXMaskingTest(test.TestCase):
controllerConfigService = ( controllerConfigService = (
self.driver.utils.find_controller_configuration_service( self.driver.utils.find_controller_configuration_service(
conn, self.data.storage_system)) conn, self.data.storage_system))
masking._remove_volume_from_sg = mock.Mock() with mock.patch.object(masking, '_remove_volume_from_sg') as mock_rm:
masking._cleanup_deletion_v3( masking._cleanup_deletion_v3(
conn, controllerConfigService, volumeInstance, extraSpecs) conn, controllerConfigService, volumeInstance, extraSpecs)
masking._remove_volume_from_sg.assert_called_with( mock_rm.assert_called_with(
conn, controllerConfigService, storageGroupInstanceName, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, extraSpecs) volumeInstance, extraSpecs, None)
# Bug 1552426 - failed rollback on V3 when MV issue # Bug 1552426 - failed rollback on V3 when MV issue
def test_check_ig_rollback(self): def test_check_ig_rollback(self):
@ -7932,7 +7932,6 @@ class VMAXMaskingTest(test.TestCase):
'pool': 'SRP_1', 'pool': 'SRP_1',
} }
igGroupName = self.data.initiatorgroup_name igGroupName = self.data.initiatorgroup_name
host = igGroupName.split("-")[1]
igInstance = masking._find_initiator_masking_group( igInstance = masking._find_initiator_masking_group(
conn, controllerConfigService, self.data.initiatorNames) conn, controllerConfigService, self.data.initiatorNames)
# path 1: The masking view creation process created a now stale # path 1: The masking view creation process created a now stale
@ -7943,7 +7942,8 @@ class VMAXMaskingTest(test.TestCase):
igGroupName, connector, extraSpecs) igGroupName, connector, extraSpecs)
(masking._last_volume_delete_initiator_group. (masking._last_volume_delete_initiator_group.
assert_called_once_with(conn, controllerConfigService, assert_called_once_with(conn, controllerConfigService,
igInstance, extraSpecs, host)) igInstance, extraSpecs,
self.data.short_host_name))
# path 2: No initiator group was created before the masking # path 2: No initiator group was created before the masking
# view process failed. # view process failed.
with mock.patch.object(masking, with mock.patch.object(masking,

View File

@ -810,7 +810,11 @@ class VMAXCommon(object):
volumename = volume['name'] volumename = volume['name']
LOG.info(_LI("Terminate connection: %(volume)s."), LOG.info(_LI("Terminate connection: %(volume)s."),
{'volume': volumename}) {'volume': volumename})
if not connector:
exception_message = (_("The connector object from nova "
"cannot be None."))
raise exception.VolumeBackendAPIException(
data=exception_message)
self._unmap_lun(volume, connector) self._unmap_lun(volume, connector)
def extend_volume(self, volume, newSize): def extend_volume(self, volume, newSize):

View File

@ -1603,13 +1603,15 @@ class VMAXMasking(object):
initiatorGroupInstance = conn.GetInstance( initiatorGroupInstance = conn.GetInstance(
foundInitiatorGroupInstanceName, LocalOnly=False) foundInitiatorGroupInstanceName, LocalOnly=False)
if initiatorGroupInstance['ElementName'] == igGroupName: if initiatorGroupInstance['ElementName'] == igGroupName:
host = igGroupName.split("-")[1] short_host_name = self.utils.get_host_short_name(
connector['host'])
LOG.debug("Searching for masking views associated with " LOG.debug("Searching for masking views associated with "
"%(igGroupName)s", "%(igGroupName)s",
{'igGroupName': igGroupName}) {'igGroupName': igGroupName})
self._last_volume_delete_initiator_group( self._last_volume_delete_initiator_group(
conn, controllerConfigService, conn, controllerConfigService,
foundInitiatorGroupInstanceName, extraSpecs, host) foundInitiatorGroupInstanceName, extraSpecs,
short_host_name)
def _get_port_group_from_masking_view( def _get_port_group_from_masking_view(
self, conn, maskingViewName, storageSystemName): self, conn, maskingViewName, storageSystemName):
@ -1870,7 +1872,8 @@ class VMAXMasking(object):
storageGroupInstanceName = None storageGroupInstanceName = None
if extraSpecs[ISV3]: if extraSpecs[ISV3]:
self._cleanup_deletion_v3( self._cleanup_deletion_v3(
conn, controllerConfigService, volumeInstance, extraSpecs) conn, controllerConfigService, volumeInstance, extraSpecs,
connector)
else: else:
if connector: if connector:
storageGroupInstanceName = ( storageGroupInstanceName = (
@ -1881,7 +1884,7 @@ class VMAXMasking(object):
self._remove_volume_from_sg( self._remove_volume_from_sg(
conn, controllerConfigService, conn, controllerConfigService,
storageGroupInstanceName, storageGroupInstanceName,
volumeInstance, extraSpecs) volumeInstance, extraSpecs, connector)
else: else:
LOG.warning(_LW("Cannot get storage from connector.")) LOG.warning(_LW("Cannot get storage from connector."))
@ -1893,13 +1896,15 @@ class VMAXMasking(object):
return storageGroupInstanceName return storageGroupInstanceName
def _cleanup_deletion_v3( def _cleanup_deletion_v3(
self, conn, controllerConfigService, volumeInstance, extraSpecs): self, conn, controllerConfigService, volumeInstance, extraSpecs,
connector=None):
"""Pre cleanup before VMAX3 deletion operation """Pre cleanup before VMAX3 deletion operation
:param conn: the ecom connection :param conn: the ecom connection
:param controllerConfigService: storage system instance name :param controllerConfigService: storage system instance name
:param volumeInstance: the volume instance :param volumeInstance: the volume instance
:param extraSpecs: the extra specifications :param extraSpecs: the extra specifications
:param connector: the connector object - default None
""" """
storageGroupInstanceNames = ( storageGroupInstanceNames = (
self.get_associated_masking_groups_from_device( self.get_associated_masking_groups_from_device(
@ -1908,20 +1913,19 @@ class VMAXMasking(object):
if storageGroupInstanceNames: if storageGroupInstanceNames:
sgNum = len(storageGroupInstanceNames) sgNum = len(storageGroupInstanceNames)
if len(storageGroupInstanceNames) > 1: if len(storageGroupInstanceNames) > 1:
LOG.warning(_LW("Volume %(volumeName)s is belong to " LOG.debug("Volume %(volumeName)s belongs to %(sgNum)s "
"%(sgNum)s storage groups."), "storage groups.",
{'volumeName': volumeInstance['ElementName'], {'volumeName': volumeInstance['ElementName'],
'sgNum': sgNum}) 'sgNum': sgNum})
for storageGroupInstanceName in storageGroupInstanceNames: for storageGroupInstanceName in storageGroupInstanceNames:
self._remove_volume_from_sg( self._remove_volume_from_sg(
conn, controllerConfigService, conn, controllerConfigService,
storageGroupInstanceName, storageGroupInstanceName, volumeInstance, extraSpecs,
volumeInstance, connector)
extraSpecs)
def _remove_volume_from_sg( def _remove_volume_from_sg(
self, conn, controllerConfigService, storageGroupInstanceName, self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, extraSpecs): volumeInstance, extraSpecs, connector=None):
"""Remove volume from storage group """Remove volume from storage group
:param conn: the ecom connection :param conn: the ecom connection
@ -1929,6 +1933,7 @@ class VMAXMasking(object):
:param storageGroupInstanceName: the SG instance name :param storageGroupInstanceName: the SG instance name
:param volumeInstance: the volume instance :param volumeInstance: the volume instance
:param extraSpecs: the extra specifications :param extraSpecs: the extra specifications
:param connector: the connector object - default None
""" """
instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False) instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False)
storageGroupName = instance['ElementName'] storageGroupName = instance['ElementName']
@ -1992,7 +1997,8 @@ class VMAXMasking(object):
conn, controllerConfigService, conn, controllerConfigService,
storageGroupInstanceName, storageGroupInstanceName,
storageGroupName, volumeInstance, storageGroupName, volumeInstance,
volumeInstance['ElementName'], extraSpecs) volumeInstance['ElementName'],
extraSpecs, connector)
else: else:
# Not the last volume so remove it from storage group # Not the last volume so remove it from storage group
self._multiple_vols_in_SG( self._multiple_vols_in_SG(
@ -2005,7 +2011,8 @@ class VMAXMasking(object):
def _last_vol_in_SG( def _last_vol_in_SG(
self, conn, controllerConfigService, storageGroupInstanceName, self, conn, controllerConfigService, storageGroupInstanceName,
storageGroupName, volumeInstance, volumeName, extraSpecs): storageGroupName, volumeInstance, volumeName, extraSpecs,
connector=None):
"""Steps if the volume is the last in a storage group. """Steps if the volume is the last in a storage group.
1. Check if the volume is in a masking view. 1. Check if the volume is in a masking view.
@ -2024,6 +2031,7 @@ class VMAXMasking(object):
:param volumeInstance: the volume instance :param volumeInstance: the volume instance
:param volumeName: the volume name :param volumeName: the volume name
:param extraSpecs: the extra specifications :param extraSpecs: the extra specifications
:param connector: the connector object
""" """
status = False status = False
LOG.debug("Only one volume remains in storage group " LOG.debug("Only one volume remains in storage group "
@ -2045,14 +2053,11 @@ class VMAXMasking(object):
maskingViewInstance = conn.GetInstance( maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False) mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName'] maskingViewName = maskingViewInstance['ElementName']
self._delete_mv_ig_and_sg(
def do_delete_mv_ig_and_sg(): conn, controllerConfigService, mvInstanceName,
return self._delete_mv_ig_and_sg( maskingViewName, storageGroupInstanceName,
conn, controllerConfigService, mvInstanceName, storageGroupName, volumeInstance, volumeName,
maskingViewName, storageGroupInstanceName, extraSpecs, mv_count, connector)
storageGroupName, volumeInstance, volumeName,
extraSpecs, mv_count)
do_delete_mv_ig_and_sg()
status = True status = True
mv_count -= 1 mv_count -= 1
return status return status
@ -2092,7 +2097,7 @@ class VMAXMasking(object):
def _delete_mv_ig_and_sg( def _delete_mv_ig_and_sg(
self, conn, controllerConfigService, mvInstanceName, self, conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName, storageGroupName, maskingViewName, storageGroupInstanceName, storageGroupName,
volumeInstance, volumeName, extraSpecs, mv_count): volumeInstance, volumeName, extraSpecs, mv_count, connector):
"""Delete the Masking view, the storage Group and the initiator group. """Delete the Masking view, the storage Group and the initiator group.
:param conn: connection to the ecom server :param conn: connection to the ecom server
@ -2105,10 +2110,11 @@ class VMAXMasking(object):
:param volumeName: the volume name :param volumeName: the volume name
:param extraSpecs: extra specs :param extraSpecs: extra specs
:param mv_count: number of masking views :param mv_count: number of masking views
:param connector: the connector object
""" """
isV3 = extraSpecs[ISV3] isV3 = extraSpecs[ISV3]
fastPolicyName = extraSpecs.get(FASTPOLICY, None) fastPolicyName = extraSpecs.get(FASTPOLICY, None)
host = maskingViewName.split("-")[1] short_host_name = self.utils.get_host_short_name(connector['host'])
storageSystemInstanceName = self.utils.find_storage_system( storageSystemInstanceName = self.utils.find_storage_system(
conn, controllerConfigService) conn, controllerConfigService)
@ -2117,10 +2123,10 @@ class VMAXMasking(object):
self._last_volume_delete_masking_view( self._last_volume_delete_masking_view(
conn, controllerConfigService, mvInstanceName, conn, controllerConfigService, mvInstanceName,
maskingViewName, extraSpecs) maskingViewName, extraSpecs)
self._last_volume_delete_initiator_group( if initiatorGroupInstanceName:
conn, controllerConfigService, self._last_volume_delete_initiator_group(
initiatorGroupInstanceName, extraSpecs, host) conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, short_host_name)
if not isV3: if not isV3:
isTieringPolicySupported, tierPolicyServiceInstanceName = ( isTieringPolicySupported, tierPolicyServiceInstanceName = (
self._get_tiering_info(conn, storageSystemInstanceName, self._get_tiering_info(conn, storageSystemInstanceName,
@ -2731,7 +2737,7 @@ class VMAXMasking(object):
def _last_volume_delete_initiator_group( def _last_volume_delete_initiator_group(
self, conn, controllerConfigService, self, conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, host=None): initiatorGroupInstanceName, extraSpecs, host):
"""Delete the initiator group. """Delete the initiator group.
Delete the Initiator group if it has been created by the VMAX driver, Delete the Initiator group if it has been created by the VMAX driver,
@ -2739,48 +2745,51 @@ class VMAXMasking(object):
:param conn: the ecom connection :param conn: the ecom connection
:param controllerConfigService: controller config service :param controllerConfigService: controller config service
:param igInstanceNames: initiator group instance name :param initiatorGroupInstanceName: initiator group instance name
:param extraSpecs: extra specifications :param extraSpecs: extra specifications
:param host: the short name of the host :param host: the short name of the host
""" """
defaultInitiatorGroupName = None
initiatorGroupInstance = conn.GetInstance(initiatorGroupInstanceName) initiatorGroupInstance = conn.GetInstance(initiatorGroupInstanceName)
initiatorGroupName = initiatorGroupInstance['ElementName'] initiatorGroupName = initiatorGroupInstance['ElementName']
protocol = self.utils.get_short_protocol_type(self.protocol)
if host: @coordination.synchronized('emc-ig-{initiatorGroupName}')
def _inner_last_volume_delete_initiator_group(initiatorGroupName):
protocol = self.utils.get_short_protocol_type(self.protocol)
defaultInitiatorGroupName = (( defaultInitiatorGroupName = ((
"OS-%(shortHostName)s-%(protocol)s-IG" "OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': host, % {'shortHostName': host,
'protocol': protocol})) 'protocol': protocol}))
if initiatorGroupName == defaultInitiatorGroupName: if initiatorGroupName == defaultInitiatorGroupName:
maskingViewInstanceNames = ( maskingViewInstanceNames = (
self.get_masking_views_by_initiator_group( self.get_masking_views_by_initiator_group(
conn, initiatorGroupInstanceName)) conn, initiatorGroupInstanceName))
if len(maskingViewInstanceNames) == 0: if len(maskingViewInstanceNames) == 0:
LOG.debug( LOG.debug(
"Last volume associated with the initiator group - " "Last volume associated with the initiator group - "
"deleting the associated initiator group " "deleting the associated initiator group "
"%(initiatorGroupName)s.", "%(initiatorGroupName)s.",
{'initiatorGroupName': initiatorGroupName})
self._delete_initiators_from_initiator_group(
conn, controllerConfigService, initiatorGroupInstanceName,
initiatorGroupName)
self._delete_initiator_group(conn, controllerConfigService,
initiatorGroupInstanceName,
initiatorGroupName, extraSpecs)
else:
LOG.warning(_LW("Initiator group %(initiatorGroupName)s is "
"associated with masking views and can't be "
"deleted. Number of associated masking view "
"is: %(nmv)d."),
{'initiatorGroupName': initiatorGroupName,
'nmv': len(maskingViewInstanceNames)})
else:
LOG.warning(_LW("Initiator group %(initiatorGroupName)s was "
"not created by the VMAX driver so will "
"not be deleted by the VMAX driver."),
{'initiatorGroupName': initiatorGroupName}) {'initiatorGroupName': initiatorGroupName})
self._delete_initiators_from_initiator_group(
conn, controllerConfigService,
initiatorGroupInstanceName, initiatorGroupName)
self._delete_initiator_group(
conn, controllerConfigService,
initiatorGroupInstanceName,
initiatorGroupName, extraSpecs)
else:
LOG.warning(_LW("Initiator group %(initiatorGroupName)s "
"is associated with masking views and "
"can't be deleted. Number of associated "
"masking view is: %(nmv)d."),
{'initiatorGroupName': initiatorGroupName,
'nmv': len(maskingViewInstanceNames)})
else:
LOG.warning(_LW("Initiator group %(initiatorGroupName)s was "
"not created by the VMAX driver so will "
"not be deleted by the VMAX driver."),
{'initiatorGroupName': initiatorGroupName})
_inner_last_volume_delete_initiator_group(initiatorGroupName)
def _create_hardware_ids( def _create_hardware_ids(
self, conn, initiatorNames, storageSystemName): self, conn, initiatorNames, storageSystemName):