From f6d5eb70a9b0b5accf3689270a5325975ab5a9cb Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Fri, 19 Jun 2015 09:47:59 -0400 Subject: [PATCH] Support SMI-S provider v8.0.3 in VMAX driver This patch made changes in the VMAX driver to support interface changes in SMI-S provider v8.0.3. * Accommodate System Name prefix change from 'SYMMETRIX+' to 'SYMMETRIX-+-'. * Delete composite volume - changed from using ReturnElementsToStoragePool to EMCReturnToStoragePool. * Extend volume - removed InPool parameter from EMCUnBindElements method. * Create snapshot/clone with multi meta members - EMC_PartialAllocOfConcreteExtentCIM was deprecated; used 'GetCompositeElements' instead and changed how we get meta members' capacities. * Create CG snapshot - A new WaitForCopyState parameter was added. * Added helper function to determine SMI-S provider and SE version Note: SMI-S provider is bundled with SE (Solutions Enabler) and they have the same version number starting from v8.0. The EMC SMI-S Provider supports the SNIA Storage Management Initiative (SMI), an ANSI standard for storage management. It supports the VMAX storage system. SE is a software product that discovers and manages VMAX storage system. Closes-Bug: #1463217 Change-Id: Ia6333396a8111f110f540c367e5018cfad6e93e4 --- cinder/tests/unit/test_emc_vmax.py | 226 +++++++++++++++--- cinder/volume/drivers/emc/emc_vmax_common.py | 59 +++-- cinder/volume/drivers/emc/emc_vmax_fc.py | 3 +- cinder/volume/drivers/emc/emc_vmax_https.py | 24 +- cinder/volume/drivers/emc/emc_vmax_iscsi.py | 3 +- .../volume/drivers/emc/emc_vmax_provision.py | 14 +- cinder/volume/drivers/emc/emc_vmax_utils.py | 148 ++++++++---- 7 files changed, 353 insertions(+), 124 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index b996ad413..892df0883 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -294,6 +294,7 @@ class EMCVMAXCommonData(object): properties = {'ConsumableBlocks': '12345', 'BlockSize': '512'} + block_size = 512 test_volume = {'name': 'vol1', 'size': 1, 'volume_name': 'vol1', @@ -308,7 +309,7 @@ class EMCVMAXCommonData(object): 'status': 'available', 'host': 'fake-host', 'NumberOfBlocks': 100, - 'BlockSize': 512 + 'BlockSize': block_size } test_volume_v2 = {'name': 'vol1', @@ -325,7 +326,7 @@ class EMCVMAXCommonData(object): 'status': 'available', 'host': 'fake-host', 'NumberOfBlocks': 100, - 'BlockSize': 512 + 'BlockSize': block_size } test_volume_v3 = {'name': 'vol1', @@ -342,9 +343,17 @@ class EMCVMAXCommonData(object): 'status': 'available', 'host': 'fake-host', 'NumberOfBlocks': 100, - 'BlockSize': 512 + 'BlockSize': block_size } - + metaHead_volume = {'DeviceID': 10, + 'ConsumableBlocks': 1000 + } + meta_volume1 = {'DeviceID': 11, + 'ConsumableBlocks': 200 + } + meta_volume2 = {'DeviceID': 12, + 'ConsumableBlocks': 300 + } test_volume_CG = {'name': 'volInCG', 'consistencygroup_id': 'abc', 'size': 1, @@ -435,6 +444,9 @@ class EMCVMAXCommonData(object): 'storagetype:slo': u'Bronze', 'storagetype:array': u'0123456789', 'isV3': True} + majorVersion = 1 + minorVersion = 2 + revNumber = 3 class FakeLookupService(object): @@ -466,7 +478,8 @@ class FakeEcomConnection(object): Type=None, EMCSRP=None, EMCSLO=None, EMCWorkload=None, EMCCollections=None, InitiatorMaskingGroup=None, DeviceMaskingGroup=None, TargetMaskingGroup=None, - ProtocolController=None, StorageID=None, IDType=None): + ProtocolController=None, StorageID=None, IDType=None, + WaitForCopyState=None): rc = 0 myjob = SE_ConcreteJob() @@ -491,7 +504,7 @@ class FakeEcomConnection(object): elif TheElements and \ TheElements[0]['DeviceID'] == '99999' and \ - MethodName == 'EMCReturnToStoragePool': + MethodName == 'ReturnElementsToStoragePool': rc = 10 myjob['status'] = 'failure' elif HardwareId: @@ -519,6 +532,13 @@ class FakeEcomConnection(object): rc = 0 ret['HardwareID'] = self.data.iscsi_initiator return rc, ret + elif MethodName == 'GetCompositeElements': + ret = {} + rc = 0 + ret['OutElements'] = [self.data.metaHead_volume, + self.data.meta_volume1, + self.data.meta_volume2] + return rc, ret job = {'Job': myjob} return rc, job @@ -561,6 +581,8 @@ class FakeEcomConnection(object): result = self._enum_repservcpbls() elif name == 'SE_StorageSynchronized_SV_SV': result = self._enum_storageSyncSvSv() + elif name == 'Symm_SRPStoragePool': + result = self._enum_srpstoragepool() else: result = self._default_enum() return result @@ -571,6 +593,8 @@ class FakeEcomConnection(object): result = self._enum_pool_details() elif name == 'SE_StorageHardwareID': result = self._enum_storhdwids() + elif name == 'SE_ManagementServerSoftwareIdentity': + result = self._enum_sw_identity() else: result = self._default_enum() return result @@ -960,7 +984,7 @@ class FakeEcomConnection(object): def _getinstance_pool(self, objectpath): pool = {} pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' - pool['ElementName'] = 'gold' + pool['ElementName'] = self.data.poolname pool['SystemName'] = self.data.storage_system pool['TotalManagedSpace'] = self.data.totalmanagedspace_bits pool['EMCSubscribedCapacity'] = self.data.subscribedcapacity_bits @@ -977,6 +1001,7 @@ class FakeEcomConnection(object): srpstoragepool = SYMM_SrpStoragePool() srpstoragepool['CreationClassName'] = ( self.data.srpstoragepool_creationclass) + srpstoragepool['ElementName'] = 'SRP_1' classcimproperty = Fake_CIMProperty() totalManagedSpace = ( @@ -1213,6 +1238,31 @@ class FakeEcomConnection(object): vols.append(failed_vol) + volumeHead = EMC_StorageVolume() + volumeHead.classname = 'Symm_StorageVolume' + blockSize = self.data.block_size + volumeHead['ConsumableBlocks'] = ( + self.data.metaHead_volume['ConsumableBlocks']) + volumeHead['BlockSize'] = blockSize + volumeHead['DeviceID'] = self.data.metaHead_volume['DeviceID'] + vols.append(volumeHead) + + metaMember1 = EMC_StorageVolume() + metaMember1.classname = 'Symm_StorageVolume' + metaMember1['ConsumableBlocks'] = ( + self.data.meta_volume1['ConsumableBlocks']) + metaMember1['BlockSize'] = blockSize + metaMember1['DeviceID'] = self.data.meta_volume1['DeviceID'] + vols.append(metaMember1) + + metaMember2 = EMC_StorageVolume() + metaMember2.classname = 'Symm_StorageVolume' + metaMember2['ConsumableBlocks'] = ( + self.data.meta_volume2['ConsumableBlocks']) + metaMember2['BlockSize'] = blockSize + metaMember2['DeviceID'] = self.data.meta_volume2['DeviceID'] + vols.append(metaMember2) + return vols def _enum_initiatorMaskingGroup(self): @@ -1463,6 +1513,15 @@ class FakeEcomConnection(object): svInstances.append(svInstance) return svInstances + def _enum_sw_identity(self): + swIdentities = [] + swIdentity = {} + swIdentity['MajorVersion'] = self.data.majorVersion + swIdentity['MinorVersion'] = self.data.minorVersion + swIdentity['RevisionNumber'] = self.data.revNumber + swIdentities.append(swIdentity) + return swIdentities + def _default_enum(self): names = [] name = {} @@ -1688,18 +1747,39 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertEqual(storageHardwareIDInstanceNames[0], self.data.iscsi_initiator) - def test_format_system_name(self): - v2array = ['SYMMETRIX', '000195900551', 'U', 'gold'] - systemnameV2 = self.driver.utils._format_system_name(v2array[0], - v2array[1], - '+') - self.assertEqual('SYMMETRIX+000195900551', systemnameV2) - - v3array = ['SYMMETRIX', '000197200056', 'SRP_1'] - systemnameV3 = self.driver.utils._format_system_name(v3array[0], - v3array[1], - '-+-') - self.assertEqual('SYMMETRIX-+-000197200056', systemnameV3) + def test_get_pool_instance_and_system_name(self): + conn = self.fake_ecom_connection() + # V2 - old '+' separator + storagesystem = {} + storagesystem['SystemName'] = self.data.storage_system + storagesystem['Name'] = self.data.storage_system + pools = conn.EnumerateInstanceNames("EMC_VirtualProvisioningPool") + poolname = 'gold' + poolinstancename, systemname = ( + self.driver.common.utils._get_pool_instance_and_system_name( + conn, pools, storagesystem, poolname)) + self.assertEqual(self.data.storage_system, systemname) + self.assertEqual(self.data.storagepoolid, + poolinstancename['InstanceID']) + # V3 - note: V2 can also have the '-+-' separator + storagesystem = {} + storagesystem['SystemName'] = self.data.storage_system_v3 + storagesystem['Name'] = self.data.storage_system_v3 + pools = conn.EnumerateInstanceNames('Symm_SRPStoragePool') + poolname = 'SRP_1' + poolinstancename, systemname = ( + self.driver.common.utils._get_pool_instance_and_system_name( + conn, pools, storagesystem, poolname)) + self.assertEqual(self.data.storage_system_v3, systemname) + self.assertEqual('SYMMETRIX-+-000197200056-+-SRP_1', + poolinstancename['InstanceID']) + # Invalid poolname + poolname = 'bogus' + poolinstancename, systemname = ( + self.driver.common.utils._get_pool_instance_and_system_name( + conn, pools, storagesystem, poolname)) + self.assertIsNone(poolinstancename) + self.assertEqual(self.data.storage_system_v3, systemname) def test_get_hardware_type(self): iqn_initiator = 'iqn.1992-04.com.emc: 50000973f006dd80' @@ -1916,19 +1996,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): volume2 = EMC_StorageVolume() volume2['name'] = 'myVol' volume2['provider_location'] = six.text_type(provider_location2) - verify_orig = self.driver.common.utils.get_existing_instance - self.driver.common.utils.get_existing_instance = mock.Mock( + verify_orig = self.driver.common.conn.GetInstance + self.driver.common.conn.GetInstance = mock.Mock( return_value=None) findlun2 = self.driver.common._find_lun(volume2) # Not found. self.assertIsNone(findlun2) - instancename2 = self.driver.utils.get_instance_name( + self.driver.utils.get_instance_name( provider_location2['classname'], keybindings2) - self.driver.common.utils.get_existing_instance.assert_called_once_with( - self.driver.common.conn, instancename2) - self.driver.common.utils.get_existing_instance.reset_mock() - self.driver.common.utils.get_existing_instance = verify_orig + self.driver.common.conn.GetInstance.assert_called_once_with( + keybindings2) + self.driver.common.conn.GetInstance.reset_mock() + self.driver.common.conn.GetInstance = verify_orig keybindings3 = {'CreationClassName': u'Symm_StorageVolume', 'SystemName': u'SYMMETRIX+000195900551', @@ -2652,7 +2732,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -2687,7 +2767,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -2748,7 +2828,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( FakeDB, @@ -2908,7 +2988,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3019,6 +3099,74 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): conn, volumeInstance, originalName) self.assertEqual(originalName, volumeInstance['ElementName']) + def test_get_smi_version(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + version = utils.get_smi_version(conn) + expected = int(str(self.data.majorVersion) + + str(self.data.minorVersion) + + str(self.data.revNumber)) + self.assertEqual(version, expected) + + def test_get_pool_name(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + poolInstanceName = {} + poolInstanceName['InstanceID'] = "SATA_GOLD1" + poolInstanceName['CreationClassName'] = 'Symm_VirtualProvisioningPool' + poolName = utils._get_pool_name(conn, poolInstanceName) + self.assertEqual(poolName, self.data.poolname) + + def test_get_meta_members_capacity_in_byte(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + memberVolumeInstanceNames = [] + volumeHead = EMC_StorageVolume() + volumeHead.classname = 'Symm_StorageVolume' + blockSize = self.data.block_size + volumeHead['ConsumableBlocks'] = ( + self.data.metaHead_volume['ConsumableBlocks']) + volumeHead['BlockSize'] = blockSize + volumeHead['DeviceID'] = self.data.metaHead_volume['DeviceID'] + memberVolumeInstanceNames.append(volumeHead) + metaMember1 = EMC_StorageVolume() + metaMember1.classname = 'Symm_StorageVolume' + metaMember1['ConsumableBlocks'] = ( + self.data.meta_volume1['ConsumableBlocks']) + metaMember1['BlockSize'] = blockSize + metaMember1['DeviceID'] = self.data.meta_volume1['DeviceID'] + memberVolumeInstanceNames.append(metaMember1) + metaMember2 = EMC_StorageVolume() + metaMember2.classname = 'Symm_StorageVolume' + metaMember2['ConsumableBlocks'] = ( + self.data.meta_volume2['ConsumableBlocks']) + metaMember2['BlockSize'] = blockSize + metaMember2['DeviceID'] = self.data.meta_volume2['DeviceID'] + memberVolumeInstanceNames.append(metaMember2) + capacities = utils.get_meta_members_capacity_in_byte( + conn, memberVolumeInstanceNames) + headSize = ( + volumeHead['ConsumableBlocks'] - + metaMember1['ConsumableBlocks'] - + metaMember2['ConsumableBlocks']) + expected = [headSize * blockSize, + metaMember1['ConsumableBlocks'] * blockSize, + metaMember2['ConsumableBlocks'] * blockSize] + self.assertEqual(capacities, expected) + + def test_get_composite_elements(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + volumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + volumeInstance = conn.GetInstance(volumeInstanceName) + memberVolumeInstanceNames = utils.get_composite_elements( + conn, volumeInstance) + expected = [self.data.metaHead_volume, + self.data.meta_volume1, + self.data.meta_volume2] + self.assertEqual(memberVolumeInstanceNames, expected) + def _cleanup(self): if self.config_file_path: bExists = os.path.exists(self.config_file_path) @@ -3393,7 +3541,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3431,7 +3579,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3481,7 +3629,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -4184,7 +4332,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'status': 'available', 'host': self.data.fake_host, 'NumberOfBlocks': 100, - 'BlockSize': 512 + 'BlockSize': self.data.block_size } common = self.driver.common common._initial_setup = mock.Mock( @@ -4217,7 +4365,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'status': 'available', 'host': self.data.fake_host, 'NumberOfBlocks': 100, - 'BlockSize': 512 + 'BlockSize': self.data.block_size } common = self.driver.common common._initial_setup = mock.Mock( @@ -4601,7 +4749,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -4639,7 +4787,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -4708,7 +4856,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_bit', + 'get_meta_members_capacity_in_byte', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -5167,7 +5315,7 @@ class EMCV3DriverTestCase(test.TestCase): cloneVol['volume_type_id'] = 'abc' cloneVol['provider_location'] = None cloneVol['NumberOfBlocks'] = 100 - cloneVol['BlockSize'] = 512 + cloneVol['BlockSize'] = self.data.block_size self.driver.create_cloned_volume(cloneVol, self.data.test_volume) @mock.patch.object( diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 604341335..36d071f26 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -55,6 +55,7 @@ STRIPECOUNT = 'storagetype:stripecount' MEMBERCOUNT = 'storagetype:membercount' STRIPED = 'striped' CONCATENATED = 'concatenated' +SMI_VERSION_8 = 800 # V3 SLO = 'storagetype:slo' WORKLOAD = 'storagetype:workload' @@ -1319,13 +1320,23 @@ class EMCVMAXCommon(object): if isinstance(loc, six.string_types): name = eval(loc) + keys = name['keybindings'] + systemName = keys['SystemName'] + + prefix1 = 'SYMMETRIX+' + prefix2 = 'SYMMETRIX-+-' + smiversion = self.utils.get_smi_version(self.conn) + if smiversion > SMI_VERSION_8 and prefix1 in systemName: + keys['SystemName'] = systemName.replace(prefix1, prefix2) + name['keybindings'] = keys instancename = self.utils.get_instance_name( name['classname'], name['keybindings']) - # Handle the case where volume cannot be found. - foundVolumeinstance = self.utils.get_existing_instance( - self.conn, instancename) + try: + foundVolumeinstance = self.conn.GetInstance(instancename) + except Exception: + foundVolumeinstance = None if foundVolumeinstance is None: LOG.debug("Volume %(volumename)s not found on the array.", @@ -1835,8 +1846,8 @@ class EMCVMAXCommon(object): if 'True' in isVolumeBound: appendVolumeInstance = ( self._unbind_and_get_volume_from_storage_pool( - conn, storageConfigService, assocPoolInstanceName, - appendVolumeInstance.path, 'appendVolume', extraSpecs)) + conn, storageConfigService, appendVolumeInstance.path, + 'appendVolume', extraSpecs)) return appendVolumeInstance @@ -1862,27 +1873,33 @@ class EMCVMAXCommon(object): return volumeInstance def _unbind_and_get_volume_from_storage_pool( - self, conn, storageConfigService, poolInstanceName, - volumeInstanceName, volumeName, extraSpecs): + self, conn, storageConfigService, volumeInstanceName, + volumeName, extraSpecs): """Unbind a volume from a pool and return the unbound volume. :param conn: the connection information to the ecom server :param storageConfigService: the storage config service instance name - :param poolInstanceName: the pool instance name :param volumeInstanceName: the volume instance name :param volumeName: string the volumeName :param extraSpecs: extra specifications :returns: unboundVolumeInstance -- the unbound volume instance """ - _rc, job = ( + rc, job = ( self.provision.unbind_volume_from_storage_pool( - conn, storageConfigService, poolInstanceName, - volumeInstanceName, + conn, storageConfigService, volumeInstanceName, volumeName, extraSpecs)) - volumeDict = self.provision.get_volume_dict_from_job(conn, job['Job']) - volumeInstance = self.utils.find_volume_instance( - self.conn, volumeDict, volumeName) + # Check that the volume is unbound + volumeInstance = conn.GetInstance(volumeInstanceName) + isVolumeBound = self.utils.is_volume_bound_to_pool( + conn, volumeInstance) + if 'False' not in isVolumeBound: + exceptionMessage = (_( + "Failed to unbind volume: %(volume)s") + % {'volume': volumeInstanceName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + return volumeInstance def _modify_and_get_composite_volume_instance( @@ -3334,12 +3351,8 @@ class EMCVMAXCommon(object): controllerConfigurationService, volumeInstance.path, volumeName, extraSpecs) - LOG.debug("Delete Volume: %(name)s Method: EMCReturnToStoragePool " - "ConfigService: %(service)s TheElement: %(vol_instance)s " - "DeviceId: %(deviceId)s.", - {'service': storageConfigService, - 'name': volumeName, - 'vol_instance': volumeInstance.path, + LOG.debug("Deleting Volume: %(name)s with deviceId: %(deviceId)s.", + {'name': volumeName, 'deviceId': deviceId}) try: rc = self.provision.delete_volume_from_pool( @@ -3466,9 +3479,9 @@ class EMCVMAXCommon(object): else: # Composite volume with meta device members. # Check if the meta members capacity. metaMemberInstanceNames = ( - self.utils.get_meta_members_of_composite_volume( - self.conn, metaHeadInstanceName)) - volumeCapacities = self.utils.get_meta_members_capacity_in_bit( + self.utils.get_composite_elements( + self.conn, sourceInstance)) + volumeCapacities = self.utils.get_meta_members_capacity_in_byte( self.conn, metaMemberInstanceNames) LOG.debug("Volume capacities: %(metasizes)s.", {'metasizes': volumeCapacities}) diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 728df347f..8f031a2d0 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -38,9 +38,10 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 2.1.2 - Clean up failed clones (bug #1440154) 2.1.3 - Fixed a problem with FAST support (bug #1435069) 2.2.0 - Add manage/unmanage + 2.2.1 - Support for SE 8.0.3 """ - VERSION = "2.2.0" + VERSION = "2.2.1" def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_vmax_https.py b/cinder/volume/drivers/emc/emc_vmax_https.py index 46b0a583f..ea08ebfc0 100644 --- a/cinder/volume/drivers/emc/emc_vmax_https.py +++ b/cinder/volume/drivers/emc/emc_vmax_https.py @@ -14,18 +14,18 @@ # under the License. import base64 +import httplib import os import socket import ssl import string import struct +import urllib from eventlet import patcher import OpenSSL from oslo_log import log as logging import six -from six.moves import http_client -from six.moves import urllib from cinder.i18n import _, _LI @@ -74,7 +74,7 @@ def get_default_ca_certs(): class OpenSSLConnectionDelegator(object): """An OpenSSL.SSL.Connection delegator. - Supplies an additional 'makefile' method which http_client requires + Supplies an additional 'makefile' method which httplib requires and is not present in OpenSSL.SSL.Connection. Note: Since it is not possible to inherit from OpenSSL.SSL.Connection a delegator must be used. @@ -89,7 +89,7 @@ class OpenSSLConnectionDelegator(object): return socket._fileobject(self.connection, *args, **kwargs) -class HTTPSConnection(http_client.HTTPSConnection): +class HTTPSConnection(httplib.HTTPSConnection): def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, ca_certs=None, no_verification=False): if not pywbemAvailable: @@ -101,9 +101,9 @@ class HTTPSConnection(http_client.HTTPSConnection): else: excp_lst = () try: - http_client.HTTPSConnection.__init__(self, host, port, - key_file=key_file, - cert_file=cert_file) + httplib.HTTPSConnection.__init__(self, host, port, + key_file=key_file, + cert_file=cert_file) self.key_file = None if key_file is None else key_file self.cert_file = None if cert_file is None else cert_file @@ -255,7 +255,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None, """Send request over HTTP. Send XML data over HTTP to the specified url. Return the - response in XML. Uses Python's build-in http_client. x509 may be a + response in XML. Uses Python's build-in httplib. x509 may be a dictionary containing the location of the SSL certificate and key files. """ @@ -274,7 +274,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None, localAuthHeader = None tryLimit = 5 - if isinstance(data, six.text_type): + if isinstance(data, unicode): data = data.encode('utf-8') data = '\n' + data @@ -309,10 +309,10 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None, h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin) for hdr in headers: - if isinstance(hdr, six.text_type): + if isinstance(hdr, unicode): hdr = hdr.encode('utf-8') s = map(lambda x: string.strip(x), string.split(hdr, ":", 1)) - h.putheader(urllib.parse.quote(s[0]), urllib.parse.quote(s[1])) + h.putheader(urllib.quote(s[0]), urllib.quote(s[1])) try: h.endheaders() @@ -328,7 +328,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None, if response.status != 200: raise pywbem.cim_http.Error('HTTP error') - except http_client.BadStatusLine as arg: + except httplib.BadStatusLine as arg: msg = (_("Bad Status line returned: %(arg)s.") % {'arg': arg}) raise pywbem.cim_http.Error(msg) diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 148c03d09..35c6a2d47 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -46,9 +46,10 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 2.1.2 - Clean up failed clones (bug #1440154) 2.1.3 - Fixed a problem with FAST support (bug #1435069) 2.2.0 - Add manage/unmanage + 2.2.1 - Support for SE 8.0.3 """ - VERSION = "2.2.0" + VERSION = "2.2.1" def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 0ef3361fb..9a234490a 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision.py @@ -64,7 +64,7 @@ class EMCVMAXProvision(object): theElements = [volumeInstanceName] rc, job = conn.InvokeMethod( - 'EMCReturnToStoragePool', storageConfigservice, + 'ReturnElementsToStoragePool', storageConfigservice, TheElements=theElements) if rc != 0: @@ -338,14 +338,13 @@ class EMCVMAXProvision(object): time.time())}) def unbind_volume_from_storage_pool( - self, conn, storageConfigService, poolInstanceName, - volumeInstanceName, volumeName, extraSpecs): + self, conn, storageConfigService, volumeInstanceName, + volumeName, extraSpecs): """Unbind a volume from a pool and return the unbound volume. :param conn: the connection information to the ecom server :param storageConfigService: the storage configuration service instance name - :param poolInstanceName: the pool instance name :param volumeInstanceName: the volume instance name :param volumeName: the volume name :param extraSpecs: additional info @@ -358,7 +357,6 @@ class EMCVMAXProvision(object): rc, job = conn.InvokeMethod( 'EMCUnBindElement', storageConfigService, - InPool=poolInstanceName, TheElement=volumeInstanceName) if rc != 0: @@ -1070,14 +1068,16 @@ class EMCVMAXProvision(object): 'relationName': relationName, 'srcGroup': srcGroupInstanceName, 'tgtGroup': tgtGroupInstanceName}) - # 8 for clone. + # SyncType 8 - clone. + # CopyState 4 - Synchronized. rc, job = conn.InvokeMethod( 'CreateGroupReplica', replicationService, RelationshipName=relationName, SourceGroup=srcGroupInstanceName, TargetGroup=tgtGroupInstanceName, - SyncType=self.utils.get_num(8, '16')) + SyncType=self.utils.get_num(8, '16'), + WaitForCopyState=self.utils.get_num(4, '16')) if rc != 0: rc, errordesc = self.utils.wait_for_job_complete(conn, job, diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 6f2f487c4..99992dfba 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -1224,13 +1224,14 @@ class EMCVMAXUtils(object): LOG.debug( "storagePoolName: %(poolName)s, storageSystemName: %(array)s.", {'poolName': storagePoolName, 'array': storageSystemName}) - poolInstanceNames = conn.EnumerateInstanceNames( - 'EMC_VirtualProvisioningPool') + storageSystemInstanceName = self.find_storageSystem(conn, + storageSystemName) + poolInstanceNames = conn.AssociatorNames( + storageSystemInstanceName, + ResultClass='EMC_VirtualProvisioningPool') for poolInstanceName in poolInstanceNames: - poolName, systemName = ( - self.parse_pool_instance_id(poolInstanceName['InstanceID'])) - if (poolName == storagePoolName and - storageSystemName in systemName): + poolName = self._get_pool_name(conn, poolInstanceName) + if (poolName == storagePoolName): # Check that the pool hasn't been recently deleted. instance = self.get_existing_instance(conn, poolInstanceName) if instance is None: @@ -1622,27 +1623,13 @@ class EMCVMAXUtils(object): :returns: foundPoolInstanceName :returns: string -- systemNameStr """ - foundPoolInstanceName = None vpoolInstanceNames = conn.AssociatorNames( storageSystemInstanceName, ResultClass='EMC_VirtualProvisioningPool') - for vpoolInstanceName in vpoolInstanceNames: - poolInstanceId = vpoolInstanceName['InstanceID'] - # Example: SYMMETRIX+000195900551+TP+Sol_Innov - poolnameStr, systemNameStr = self.parse_pool_instance_id( - poolInstanceId) - if poolnameStr is not None and systemNameStr is not None: - if six.text_type(poolNameInStr) == six.text_type(poolnameStr): - # check that the pool hasn't recently been deleted. - try: - conn.GetInstance(vpoolInstanceName) - foundPoolInstanceName = vpoolInstanceName - except Exception: - foundPoolInstanceName = None - break - - return foundPoolInstanceName, systemNameStr + return self._get_pool_instance_and_system_name( + conn, vpoolInstanceNames, storageSystemInstanceName, + poolNameInStr) def get_pool_and_system_name_v3( self, conn, storageSystemInstanceName, poolNameInStr): @@ -1654,27 +1641,54 @@ class EMCVMAXUtils(object): :returns: foundPoolInstanceName :returns: string -- systemNameStr """ - foundPoolInstanceName = None srpPoolInstanceNames = conn.AssociatorNames( storageSystemInstanceName, ResultClass='Symm_SRPStoragePool') - for srpPoolInstanceName in srpPoolInstanceNames: - poolInstanceID = srpPoolInstanceName['InstanceID'] + return self._get_pool_instance_and_system_name( + conn, srpPoolInstanceNames, storageSystemInstanceName, + poolNameInStr) + + def _get_pool_instance_and_system_name( + self, conn, poolInstanceNames, storageSystemInstanceName, + poolname): + """Get the pool instance and the system name + + :param conn: the ecom connection + :param poolInstanceNames: list of pool instances + :param storageSystemInstanceName: storage system instance name + :param poolname: pool name (string) + :returns: foundPoolInstanceName, systemNameStr + """ + foundPoolInstanceName = None + poolnameStr = None + systemNameStr = storageSystemInstanceName['Name'] + for poolInstanceName in poolInstanceNames: # Example: SYMMETRIX-+-000196700535-+-SR-+-SRP_1 - poolnameStr, systemNameStr = self.parse_pool_instance_id_v3( - poolInstanceID) - if poolnameStr is not None and systemNameStr is not None: - if six.text_type(poolNameInStr) == six.text_type(poolnameStr): - try: - conn.GetInstance(srpPoolInstanceName) - foundPoolInstanceName = srpPoolInstanceName - except Exception: - foundPoolInstanceName = None + # Example: SYMMETRIX+000195900551+TP+Sol_Innov + poolnameStr = self._get_pool_name(conn, poolInstanceName) + if poolnameStr is not None: + if six.text_type(poolname) == six.text_type(poolnameStr): + foundPoolInstanceName = poolInstanceName break return foundPoolInstanceName, systemNameStr + def _get_pool_name(self, conn, poolInstanceName): + """The pool name from the instance + + :param conn: the ecom connection + :param poolInstanceName: the pool instance + :returns: poolnameStr + """ + poolnameStr = None + try: + poolInstance = conn.GetInstance(poolInstanceName) + poolnameStr = poolInstance['ElementName'] + except Exception: + pass + return poolnameStr + def find_storageSystem(self, conn, arrayStr): """Find an array instance name given the array name. @@ -1831,21 +1845,28 @@ class EMCVMAXUtils(object): LOG.debug("metaMembers: %(members)s.", {'members': metaMembers}) return metaMembers - def get_meta_members_capacity_in_bit(self, conn, volumeInstanceNames): - """Get the capacity in bits of all meta device member volumes. + def get_meta_members_capacity_in_byte(self, conn, volumeInstanceNames): + """Get the capacity in byte of all meta device member volumes. :param conn: the ecom connection :param volumeInstanceNames: array contains meta device member volumes :returns: array contains capacities of each member device in bits """ - capacitiesInBit = [] + capacitiesInByte = [] + headVolume = conn.GetInstance(volumeInstanceNames[0]) + totalSizeInByte = ( + headVolume['ConsumableBlocks'] * headVolume['BlockSize']) + volumeInstanceNames.pop(0) for volumeInstanceName in volumeInstanceNames: volumeInstance = conn.GetInstance(volumeInstanceName) numOfBlocks = volumeInstance['ConsumableBlocks'] blockSize = volumeInstance['BlockSize'] - volumeSizeInbits = numOfBlocks * blockSize - capacitiesInBit.append(volumeSizeInbits) - return capacitiesInBit + volumeSizeInByte = numOfBlocks * blockSize + capacitiesInByte.append(volumeSizeInByte) + totalSizeInByte = totalSizeInByte - volumeSizeInByte + + capacitiesInByte.insert(0, totalSizeInByte) + return capacitiesInByte def get_existing_instance(self, conn, instanceName): """Check that the instance name still exists and return the instance. @@ -2112,3 +2133,48 @@ class EMCVMAXUtils(object): {'source': sourceDeviceId, 'storageSystem': storageSystem}) return foundSyncInstanceName + + def get_smi_version(self, conn): + intVersion = 0 + swIndentityInstances = conn.EnumerateInstances( + 'SE_ManagementServerSoftwareIdentity') + if swIndentityInstances: + swIndentityInstance = swIndentityInstances[0] + majorVersion = swIndentityInstance['MajorVersion'] + minorVersion = swIndentityInstance['MinorVersion'] + revisionNumber = swIndentityInstance['RevisionNumber'] + + intVersion = int(str(majorVersion) + str(minorVersion) + + str(revisionNumber)) + + LOG.debug("Major version: %(majV)lu, Minor version: %(minV)lu, " + "Revision number: %(revNum)lu, Version: %(intV)lu.", + {'majV': majorVersion, + 'minV': minorVersion, + 'revNum': revisionNumber, + 'intV': intVersion}) + return intVersion + + def get_composite_elements( + self, conn, volumeInstance): + """Get the meta members of a composite volume. + + :param conn: ECOM connection + :param volumeInstance: the volume instance + :returns memberVolumes: a list of meta members + """ + memberVolumes = None + storageSystemName = volumeInstance['SystemName'] + elementCompositionService = self.find_element_composition_service( + conn, storageSystemName) + rc, ret = conn.InvokeMethod( + 'GetCompositeElements', + elementCompositionService, + TheElement=volumeInstance.path) + + if 'OutElements' in ret: + LOG.debug("Get composite elements of volume " + "%(volume)s rc=%(rc)d, ret=%(ret)s", + {'volume': volumeInstance.path, 'rc': rc, 'ret': ret}) + memberVolumes = ret['OutElements'] + return memberVolumes -- 2.45.2