From 794e27c78255c94084b54821d44a36fd8f2096d7 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Wed, 23 Sep 2015 11:22:43 -0400 Subject: [PATCH] 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 --- cinder/tests/unit/test_emc_vmax.py | 32 +++++++++++++++-- cinder/volume/drivers/emc/emc_vmax_common.py | 35 +++++++++++++------ cinder/volume/drivers/emc/emc_vmax_iscsi.py | 3 +- cinder/volume/drivers/emc/emc_vmax_masking.py | 2 +- 4 files changed, 56 insertions(+), 16 deletions(-) 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): -- 2.45.2