From: Xing Yang <xing.yang@emc.com>
Date: Wed, 23 Sep 2015 15:22:43 +0000 (-0400)
Subject: Fix VMAX live migration problem
X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=5268a8d61beee1b18d14fd3738e763a0a61b00f1;p=openstack-build%2Fcinder-build.git

Fix VMAX live migration problem

Live migration fails for VMAX because the host is not taken into
account when determining whether a volume is masked or not. For
live migration it is necessary to know which host a volume is masked
to. This patch fixes the problem by checking which host the volume
is masked to.

Change-Id: I693e29f7b26145bd1fd357b7d98377e26bfdfed8
Closes-Bug: #1498964
(cherry picked from commit 794e27c78255c94084b54821d44a36fd8f2096d7)
---

diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py
index 41be1e0a4..ceee88ec5 100644
--- a/cinder/tests/unit/test_emc_vmax.py
+++ b/cinder/tests/unit/test_emc_vmax.py
@@ -134,9 +134,9 @@ class Fake_CIMProperty(object):
         cimproperty.value = '10000000000'
         return cimproperty
 
-    def fake_getElementNameCIMProperty(self):
+    def fake_getElementNameCIMProperty(self, name):
         cimproperty = Fake_CIMProperty()
-        cimproperty.value = 'OS-myhost-MV'
+        cimproperty.value = name
         return cimproperty
 
     def fake_getSupportedReplicationTypes(self):
@@ -815,7 +815,7 @@ class FakeEcomConnection(object):
 
         classcimproperty = Fake_CIMProperty()
         elementName = (
-            classcimproperty.fake_getElementNameCIMProperty())
+            classcimproperty.fake_getElementNameCIMProperty('OS-myhost-MV'))
         properties = {u'ElementName': elementName}
         antecedent.properties = properties
 
@@ -824,6 +824,19 @@ class FakeEcomConnection(object):
         unitname['CreationClassName'] = self.data.unit_creationclass
         unitnames.append(unitname)
 
+        # Second masking
+        unitname2 = unitname.copy()
+        elementName2 = (
+            classcimproperty.fake_getElementNameCIMProperty('OS-fakehost-MV'))
+        properties2 = {u'ElementName': elementName2}
+
+        antecedent2 = SYMM_LunMasking()
+        antecedent2['CreationClassName'] = self.data.lunmask_creationclass2
+        antecedent2['SystemName'] = self.data.storage_system
+
+        antecedent2.properties = properties2
+        unitname2['Antecedent'] = antecedent2
+        unitnames.append(unitname2)
         return unitnames
 
     def _default_ref(self, objectpath):
@@ -1790,6 +1803,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
     def fake_is_v3(self, conn, serialNumber):
         return False
 
+    def test_find_device_number(self):
+        host = 'myhost'
+        data = (
+            self.driver.common.find_device_number(self.data.test_volume_v2,
+                                                  host))
+        self.assertEqual('OS-myhost-MV', data['maskingview'])
+        host = 'bogushost'
+        data = (
+            self.driver.common.find_device_number(self.data.test_volume_v2,
+                                                  host))
+        # Empty dict
+        self.assertFalse(data)
+
     def test_unbind_and_get_volume_from_storage_pool(self):
         conn = self.fake_ecom_connection()
         common = self.driver.common
diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py
index 35ff88afc..070e58bdc 100644
--- a/cinder/volume/drivers/emc/emc_vmax_common.py
+++ b/cinder/volume/drivers/emc/emc_vmax_common.py
@@ -320,7 +320,7 @@ class EMCVMAXCommon(object):
         LOG.info(_LI("Unmap volume: %(volume)s."),
                  {'volume': volumename})
 
-        device_info = self.find_device_number(volume)
+        device_info = self.find_device_number(volume, connector['host'])
         device_number = device_info['hostlunid']
         if device_number is None:
             LOG.info(_LI("Volume %s is not mapped. No volume to unmap."),
@@ -374,7 +374,7 @@ class EMCVMAXCommon(object):
         LOG.info(_LI("Initialize connection: %(volume)s."),
                  {'volume': volumeName})
         self.conn = self._get_ecom_connection()
-        deviceInfoDict = self.find_device_number(volume)
+        deviceInfoDict = self.find_device_number(volume, connector['host'])
 
         if ('hostlunid' in deviceInfoDict and
                 deviceInfoDict['hostlunid'] is not None):
@@ -423,7 +423,7 @@ class EMCVMAXCommon(object):
             self.conn, maskingViewDict, extraSpecs)
 
         # Find host lun id again after the volume is exported to the host.
-        deviceInfoDict = self.find_device_number(volume)
+        deviceInfoDict = self.find_device_number(volume, connector['host'])
         if 'hostlunid' not in deviceInfoDict:
             # Did not successfully attach to host,
             # so a rollback for FAST is required.
@@ -1412,15 +1412,18 @@ class EMCVMAXCommon(object):
                    'initiator': foundinitiatornames})
         return foundinitiatornames
 
-    def find_device_number(self, volume):
+    def find_device_number(self, volume, host):
         """Given the volume dict find a device number.
 
         Find a device number that a host can see
         for a volume.
 
         :param volume: the volume dict
+        :param host: host from connector
         :returns: dict -- the data dict
         """
+        maskedvols = []
+        data = {}
         foundNumDeviceNumber = None
         foundMaskingViewName = None
         volumeName = volume['name']
@@ -1448,23 +1451,33 @@ class EMCVMAXCommon(object):
                     if properties[0] == 'ElementName':
                         cimProperties = properties[1]
                         foundMaskingViewName = cimProperties.value
-                        break
 
-                break
+                devicedict = {'hostlunid': foundNumDeviceNumber,
+                              'storagesystem': storageSystemName,
+                              'maskingview': foundMaskingViewName}
+                maskedvols.append(devicedict)
 
-        if foundNumDeviceNumber is None:
+        if not maskedvols:
             LOG.debug(
                 "Device number not found for volume "
                 "%(volumeName)s %(volumeInstance)s.",
                 {'volumeName': volumeName,
                  'volumeInstance': volumeInstance.path})
+        else:
+            hoststr = ("-%(host)s-"
+                       % {'host': host})
 
-        data = {'hostlunid': foundNumDeviceNumber,
-                'storagesystem': storageSystemName,
-                'maskingview': foundMaskingViewName}
+            for maskedvol in maskedvols:
+                if hoststr.lower() in maskedvol['maskingview'].lower():
+                    data = maskedvol
+                    break
+            if not data:
+                LOG.warning(_LW(
+                    "Volume is masked but not to host %(host)s as "
+                    "expected. Returning empty dictionary."),
+                    {'host': hoststr})
 
         LOG.debug("Device info: %(data)s.", {'data': data})
-
         return data
 
     def get_target_wwns(self, storageSystem, connector):
diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py
index 179cada10..8e97262ec 100644
--- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py
+++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py
@@ -212,7 +212,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
         LOG.debug("ISCSI Discovery: Found %s", location)
         properties['target_discovered'] = True
 
-        device_info = self.common.find_device_number(volume)
+        device_info = self.common.find_device_number(
+            volume, connector['host'])
 
         if device_info is None or device_info['hostlunid'] is None:
             exception_message = (_("Cannot find device number for volume "
diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py
index 710738233..e0e610084 100644
--- a/cinder/volume/drivers/emc/emc_vmax_masking.py
+++ b/cinder/volume/drivers/emc/emc_vmax_masking.py
@@ -60,7 +60,7 @@ class EMCVMAXMasking(object):
             return self.get_or_create_masking_view_and_map_lun(conn,
                                                                maskingViewDict,
                                                                extraSpecs)
-        do_get_or_create_masking_view_and_map_lun()
+        return do_get_or_create_masking_view_and_map_lun()
 
     def get_or_create_masking_view_and_map_lun(self, conn, maskingViewDict,
                                                extraSpecs):