EMC VMAX - not cleaning up HW Resource WWPN initiators
On cleanup, the last volume is unmapped and the masking view and storage group are deleted. Now the initiator group and the associated StorageHardwareIDs are also deleted as long as that initiator group is no longer contained in another masking view. Change-Id: Ib159f719a9c5cbac5c6ea0b7d9474a64a0335b71 Closes-Bug: #1504192
This commit is contained in:
parent
a5c65bbb7d
commit
921230a5ec
@ -702,7 +702,7 @@ class FakeEcomConnection(object):
|
||||
|
||||
def Associators(self, objectpath, ResultClass='EMC_StorageHardwareID'):
|
||||
result = None
|
||||
if ResultClass == 'EMC_StorageHardwareID':
|
||||
if '_StorageHardwareID' in ResultClass:
|
||||
result = self._assoc_hdwid()
|
||||
elif ResultClass == 'EMC_iSCSIProtocolEndpoint':
|
||||
result = self._assoc_endpoint()
|
||||
@ -1554,6 +1554,7 @@ class FakeEcomConnection(object):
|
||||
hdwid = SE_StorageHardwareID()
|
||||
hdwid['CreationClassName'] = self.data.hardwareid_creationclass
|
||||
hdwid['StorageID'] = self.data.connector['wwpns'][0]
|
||||
hdwid['InstanceID'] = "W-+-" + self.data.connector['wwpns'][0]
|
||||
|
||||
hdwid.path = hdwid
|
||||
storhdwids.append(hdwid)
|
||||
@ -2478,6 +2479,95 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
|
||||
conn, controllerConfigService, storageGroupInstanceName,
|
||||
storageGroupName, volumeInstanceName, volumeName, extraSpecs)
|
||||
|
||||
# Bug 1504192 - if the last volume is being unmapped and the masking view
|
||||
# goes away, cleanup the initiators and associated initiator group.
|
||||
def test_delete_initiators_from_initiator_group(self):
|
||||
conn = self.fake_ecom_connection()
|
||||
controllerConfigService = (
|
||||
self.driver.utils.find_controller_configuration_service(
|
||||
conn, self.data.storage_system))
|
||||
initiatorGroupName = self.data.initiatorgroup_name
|
||||
initiatorGroupInstanceName = (
|
||||
self.driver.common.masking._get_initiator_group_from_masking_view(
|
||||
conn, self.data.lunmaskctrl_name, self.data.storage_system))
|
||||
conn.InvokeMethod = mock.Mock(return_value=1)
|
||||
# Deletion of initiators failed.
|
||||
self.driver.common.masking._delete_initiators_from_initiator_group(
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
initiatorGroupName)
|
||||
conn.InvokeMethod = mock.Mock(return_value=0)
|
||||
# Deletion of initiators successful.
|
||||
self.driver.common.masking._delete_initiators_from_initiator_group(
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
initiatorGroupName)
|
||||
|
||||
# Bug 1504192 - if the last volume is being unmapped and the masking view
|
||||
# goes away, cleanup the initiators and associated initiator group.
|
||||
def test_last_volume_delete_initiator_group_exception(self):
|
||||
extraSpecs = {'volume_backend_name': 'ISCSINoFAST'}
|
||||
conn = self.fake_ecom_connection()
|
||||
controllerConfigService = (
|
||||
self.driver.utils.find_controller_configuration_service(
|
||||
conn, self.data.storage_system))
|
||||
initiatorGroupInstanceName = (
|
||||
self.driver.common.masking._get_initiator_group_from_masking_view(
|
||||
conn, self.data.lunmaskctrl_name, self.data.storage_system))
|
||||
job = {
|
||||
'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}}
|
||||
conn.InvokeMethod = mock.Mock(return_value=(4096, job))
|
||||
self.driver.common.masking.get_masking_views_by_initiator_group = (
|
||||
mock.Mock(return_value=[]))
|
||||
self.driver.common.masking._delete_initiators_from_initiator_group = (
|
||||
mock.Mock(return_value=True))
|
||||
self.driver.common.masking.utils.wait_for_job_complete = (
|
||||
mock.Mock(return_value=(2, 'failure')))
|
||||
# Exception occurrs while deleting the initiator group.
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.common.masking._last_volume_delete_initiator_group,
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
extraSpecs)
|
||||
|
||||
# Bug 1504192 - if the last volume is being unmapped and the masking view
|
||||
# goes away, cleanup the initiators and associated initiator group.
|
||||
def test_last_volume_delete_initiator_group(self):
|
||||
extraSpecs = {'volume_backend_name': 'ISCSINoFAST'}
|
||||
conn = self.fake_ecom_connection()
|
||||
controllerConfigService = (
|
||||
self.driver.utils.find_controller_configuration_service(
|
||||
conn, self.data.storage_system))
|
||||
initiatorGroupName = self.data.initiatorgroup_name
|
||||
initiatorGroupInstanceName = (
|
||||
self.driver.common.masking._get_initiator_group_from_masking_view(
|
||||
conn, self.data.lunmaskctrl_name, self.data.storage_system))
|
||||
self.assertEqual(initiatorGroupName,
|
||||
conn.GetInstance(
|
||||
initiatorGroupInstanceName)['ElementName'])
|
||||
# masking view is associated with the initiator group and initiator
|
||||
# group will not be deleted.
|
||||
self.driver.common.masking._last_volume_delete_initiator_group(
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
extraSpecs)
|
||||
self.driver.common.masking.get_masking_views_by_initiator_group = (
|
||||
mock.Mock(return_value=[]))
|
||||
self.driver.common.masking._delete_initiators_from_initiator_group = (
|
||||
mock.Mock(return_value=True))
|
||||
# No Masking view and initiators associated with the Initiator group
|
||||
# and initiator group will be deleted.
|
||||
self.driver.common.masking._last_volume_delete_initiator_group(
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
extraSpecs)
|
||||
job = {
|
||||
'Job': {'InstanceID': '9999', 'status': 'success', 'type': None}}
|
||||
conn.InvokeMethod = mock.Mock(return_value=(4096, job))
|
||||
self.driver.common.masking.utils.wait_for_job_complete = (
|
||||
mock.Mock(return_value=(0, 'success')))
|
||||
# Deletion of initiator group is successful after waiting for job
|
||||
# to complete.
|
||||
self.driver.common.masking._last_volume_delete_initiator_group(
|
||||
conn, controllerConfigService, initiatorGroupInstanceName,
|
||||
extraSpecs)
|
||||
|
||||
# Tests removal of last volume in a storage group V2
|
||||
def test_remove_and_reset_members(self):
|
||||
extraSpecs = {'volume_backend_name': 'GOLD_BE',
|
||||
@ -5678,7 +5768,7 @@ class EMCV3DriverTestCase(test.TestCase):
|
||||
conn, controllerConfigService, storageGroupName))
|
||||
|
||||
vol = self.default_vol()
|
||||
self.driver.common.masking._delete_mv_and_sg = mock.Mock()
|
||||
self.driver.common.masking._delete_mv_ig_and_sg = mock.Mock()
|
||||
self.assertTrue(self.driver.common.masking._last_vol_in_SG(
|
||||
conn, controllerConfigService, storageGroupInstanceName,
|
||||
storageGroupName, vol, vol['name'], extraSpecs))
|
||||
|
@ -1817,9 +1817,11 @@ class EMCVMAXMasking(object):
|
||||
"""Steps if the volume is the last in a storage group.
|
||||
|
||||
1. Check if the volume is in a masking view.
|
||||
2. If it is in a masking view, delete the masking view,
|
||||
remove the volume from the storage group, and delete
|
||||
the storage group.
|
||||
2. If it is in a masking view, delete the masking view, remove the
|
||||
initiators from the initiator group and delete the initiator
|
||||
group if there are no other masking views associated with the
|
||||
initiator group, remove the volume from the storage group, and
|
||||
delete the storage group.
|
||||
3. If it is not in a masking view, remove the volume from the
|
||||
storage group and delete the storage group.
|
||||
|
||||
@ -1853,13 +1855,13 @@ class EMCVMAXMasking(object):
|
||||
|
||||
@lockutils.synchronized(maskingViewName,
|
||||
"emc-mv-", True)
|
||||
def do_delete_mv_and_sg():
|
||||
return self._delete_mv_and_sg(
|
||||
def do_delete_mv_ig_and_sg():
|
||||
return self._delete_mv_ig_and_sg(
|
||||
conn, controllerConfigService, mvInstanceName,
|
||||
maskingViewName, storageGroupInstanceName,
|
||||
storageGroupName, volumeInstance, volumeName,
|
||||
extraSpecs)
|
||||
do_delete_mv_and_sg()
|
||||
do_delete_mv_ig_and_sg()
|
||||
status = True
|
||||
else:
|
||||
# Remove the volume from the storage group and delete the SG.
|
||||
@ -1925,11 +1927,11 @@ class EMCVMAXMasking(object):
|
||||
"End: number of volumes in masking storage group: %(numVol)d.",
|
||||
{'numVol': len(volumeInstanceNames)})
|
||||
|
||||
def _delete_mv_and_sg(self, conn, controllerConfigService, mvInstanceName,
|
||||
maskingViewName, storageGroupInstanceName,
|
||||
storageGroupName, volumeInstance, volumeName,
|
||||
extraSpecs):
|
||||
"""Delete the Masking view and the Storage Group.
|
||||
def _delete_mv_ig_and_sg(
|
||||
self, conn, controllerConfigService, mvInstanceName,
|
||||
maskingViewName, storageGroupInstanceName, storageGroupName,
|
||||
volumeInstance, volumeName, extraSpecs):
|
||||
"""Delete the Masking view, the storage Group and the initiator group.
|
||||
|
||||
Also does necessary cleanup like removing the policy from the
|
||||
storage group for V2 and returning the volume to the default
|
||||
@ -1950,10 +1952,15 @@ class EMCVMAXMasking(object):
|
||||
|
||||
storageSystemInstanceName = self.utils.find_storage_system(
|
||||
conn, controllerConfigService)
|
||||
|
||||
initiatorGroupInstanceName = (
|
||||
self.get_initiator_group_from_masking_view(conn, mvInstanceName))
|
||||
self._last_volume_delete_masking_view(
|
||||
conn, controllerConfigService, mvInstanceName,
|
||||
maskingViewName, extraSpecs)
|
||||
self._last_volume_delete_initiator_group(
|
||||
conn, controllerConfigService,
|
||||
initiatorGroupInstanceName, extraSpecs)
|
||||
|
||||
if not isV3:
|
||||
isTieringPolicySupported, tierPolicyServiceInstanceName = (
|
||||
self._get_tiering_info(conn, storageSystemInstanceName,
|
||||
@ -2261,10 +2268,13 @@ class EMCVMAXMasking(object):
|
||||
self, conn, initiatorGroupInstanceName):
|
||||
"""Given initiator group, retrieve the masking view instance name.
|
||||
|
||||
Retrieve the list of masking view instances associated with the
|
||||
initiator group instance name.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param initiatorGroupInstanceName: the instance name of the
|
||||
initiator group
|
||||
:returns: masking view instance names
|
||||
:returns: list of masking view instance names
|
||||
"""
|
||||
mvInstanceNames = conn.AssociatorNames(
|
||||
initiatorGroupInstanceName, ResultClass='Symm_LunMaskingView')
|
||||
@ -2299,13 +2309,13 @@ class EMCVMAXMasking(object):
|
||||
initiatorGroupInstanceNames = conn.AssociatorNames(
|
||||
maskingViewInstanceName, ResultClass='SE_InitiatorMaskingGroup')
|
||||
if len(initiatorGroupInstanceNames) > 0:
|
||||
LOG.debug("Found initiator group %(pg)s in masking view %(mv)s.",
|
||||
{'pg': initiatorGroupInstanceNames[0],
|
||||
LOG.debug("Found initiator group %(ig)s in masking view %(mv)s.",
|
||||
{'ig': initiatorGroupInstanceNames[0],
|
||||
'mv': maskingViewInstanceName})
|
||||
return initiatorGroupInstanceNames[0]
|
||||
else:
|
||||
LOG.warning(_LW("No port group found in masking view %(mv)s."),
|
||||
{'mv': maskingViewInstanceName})
|
||||
LOG.warning(_LW("No Initiator group found in masking view "
|
||||
"%(mv)s."), {'mv': maskingViewInstanceName})
|
||||
|
||||
def _get_sg_or_mv_associated_with_initiator(
|
||||
self, conn, controllerConfigService, volumeInstanceName,
|
||||
@ -2436,7 +2446,7 @@ class EMCVMAXMasking(object):
|
||||
if rc != 0:
|
||||
exceptionMessage = (_(
|
||||
"Error Deleting Group: %(storageGroupName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'storageGroupName': storageGroupName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
@ -2444,6 +2454,146 @@ class EMCVMAXMasking(object):
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
def _delete_initiator_group(self, conn, controllerConfigService,
|
||||
initiatorGroupInstanceName, initiatorGroupName,
|
||||
extraSpecs):
|
||||
"""Delete an initiatorGroup.
|
||||
|
||||
:param conn - connection to the ecom server
|
||||
:param controllerConfigService - controller config service
|
||||
:param initiatorGroupInstanceName - the initiator group instance name
|
||||
:param initiatorGroupName - initiator group name
|
||||
:param extraSpecs: extra specifications
|
||||
"""
|
||||
|
||||
rc, job = conn.InvokeMethod(
|
||||
'DeleteGroup',
|
||||
controllerConfigService,
|
||||
MaskingGroup=initiatorGroupInstanceName,
|
||||
Force=True)
|
||||
|
||||
if rc != 0:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
|
||||
extraSpecs)
|
||||
if rc != 0:
|
||||
exceptionMessage = (_(
|
||||
"Error Deleting Initiator Group: %(initiatorGroupName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'initiatorGroupName': initiatorGroupName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
else:
|
||||
LOG.debug("Initiator group %(initiatorGroupName)s "
|
||||
"is successfully deleted.",
|
||||
{'initiatorGroupName': initiatorGroupName})
|
||||
else:
|
||||
LOG.debug("Initiator group %(initiatorGroupName)s "
|
||||
"is successfully deleted.",
|
||||
{'initiatorGroupName': initiatorGroupName})
|
||||
|
||||
def _delete_storage_hardware_id(self,
|
||||
conn,
|
||||
hardwareIdManagementService,
|
||||
hardwareIdPath):
|
||||
"""Delete given initiator path
|
||||
|
||||
Delete the initiator. Do not rise exception or failure if deletion
|
||||
fails due to any reasons.
|
||||
|
||||
:param conn - connection to the ecom server
|
||||
:param hardwareIdManagementService - hardware id management service
|
||||
:param hardwareIdPath - The path of the initiator object
|
||||
"""
|
||||
ret = conn.InvokeMethod('DeleteStorageHardwareID',
|
||||
hardwareIdManagementService,
|
||||
HardwareID = hardwareIdPath)
|
||||
if ret == 0:
|
||||
LOG.debug("Deletion of initiator path %(hardwareIdPath)s "
|
||||
"is successful.", {'hardwareIdPath': hardwareIdPath})
|
||||
else:
|
||||
LOG.warning(_LW("Deletion of initiator path %(hardwareIdPath)s "
|
||||
"is failed."), {'hardwareIdPath': hardwareIdPath})
|
||||
|
||||
def _delete_initiators_from_initiator_group(self, conn,
|
||||
controllerConfigService,
|
||||
initiatorGroupInstanceName,
|
||||
initiatorGroupName):
|
||||
"""Delete initiators
|
||||
|
||||
Delete all initiators associated with the initiator group instance.
|
||||
Cleanup whatever is possible. It will not return any failure or
|
||||
rise exception if deletion fails due to any reasons.
|
||||
|
||||
:param conn - connection to the ecom server
|
||||
:param controllerConfigService - controller config service
|
||||
:param initiatorGroupInstanceName - the initiator group instance name
|
||||
"""
|
||||
storageHardwareIdInstanceNames = (
|
||||
conn.AssociatorNames(initiatorGroupInstanceName,
|
||||
ResultClass='SE_StorageHardwareID'))
|
||||
if len(storageHardwareIdInstanceNames) == 0:
|
||||
LOG.debug("No initiators found in Initiator group "
|
||||
"%(initiatorGroupName)s.",
|
||||
{'initiatorGroupName': initiatorGroupName})
|
||||
return
|
||||
storageSystemName = controllerConfigService['SystemName']
|
||||
hardwareIdManagementService = (
|
||||
self.utils.find_storage_hardwareid_service(conn,
|
||||
storageSystemName))
|
||||
for storageHardwareIdInstanceName in storageHardwareIdInstanceNames:
|
||||
initiatorName = storageHardwareIdInstanceName['InstanceID']
|
||||
hardwareIdPath = storageHardwareIdInstanceName
|
||||
LOG.debug("Initiator %(initiatorName)s "
|
||||
"will be deleted from the Initiator group "
|
||||
"%(initiatorGroupName)s. HardwareIdPath is "
|
||||
"%(hardwareIdPath)s.",
|
||||
{'initiatorName': initiatorName,
|
||||
'initiatorGroupName': initiatorGroupName,
|
||||
'hardwareIdPath': hardwareIdPath})
|
||||
self._delete_storage_hardware_id(conn,
|
||||
hardwareIdManagementService,
|
||||
hardwareIdPath)
|
||||
|
||||
def _last_volume_delete_initiator_group(
|
||||
self, conn, controllerConfigService,
|
||||
initiatorGroupInstanceName, extraSpecs):
|
||||
"""Delete the initiator group
|
||||
|
||||
Delete the Initiator group if there are no masking views associated
|
||||
with the initiator group.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param controllerConfigService: controller config service
|
||||
:param igInstanceNames: initiator group instance name
|
||||
:param extraSpecs: extra specifications
|
||||
"""
|
||||
maskingViewInstanceNames = self.get_masking_views_by_initiator_group(
|
||||
conn, initiatorGroupInstanceName)
|
||||
initiatorGroupInstance = conn.GetInstance(initiatorGroupInstanceName)
|
||||
initiatorGroupName = initiatorGroupInstance['ElementName']
|
||||
|
||||
if len(maskingViewInstanceNames) == 0:
|
||||
LOG.debug(
|
||||
"Last volume is associated with the initiator group, deleting "
|
||||
"the associated initiator group %(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)})
|
||||
|
||||
def _create_hardware_ids(
|
||||
self, conn, initiatorNames, storageSystemName):
|
||||
"""Create hardwareIds for initiator(s).
|
||||
|
Loading…
x
Reference in New Issue
Block a user