]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
EMC VMAX - not cleaning up HW Resource WWPN initiators
authorHelen Walsh <helen.walsh@emc.com>
Tue, 16 Feb 2016 22:33:24 +0000 (22:33 +0000)
committerHelen Walsh <helen.walsh@emc.com>
Mon, 22 Feb 2016 10:37:58 +0000 (10:37 +0000)
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

cinder/tests/unit/test_emc_vmax.py
cinder/volume/drivers/emc/emc_vmax_masking.py

index 0d059bf31c1ff07bd4f3f5da81b5647af0fac472..23e024c34c22d4332ddb579498979eb379cc0861 100644 (file)
@@ -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))
index d33c5462c137438301bc5c6d8c12fb911de94365..66127d90c4aa2b6a03c9d646931c0f587962abfc 100644 (file)
@@ -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).