From 3cf36e1f1db3e2ebd582248e9dc30dfcc0c41676 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Tue, 9 Jun 2015 20:57:32 -0400 Subject: [PATCH] Add multiple pools support to VMAX driver This patch adds multiple pools support to the VMAX driver. Also changed \ to () in a few places. implements blueprint emc-vmax-multiple-pools Change-Id: Iae0c658c808168fa5279b8dbcc6734632a98642d --- cinder/tests/unit/test_emc_vmax.py | 1726 ++++++++++++----- cinder/volume/drivers/emc/emc_vmax_common.py | 521 +++-- cinder/volume/drivers/emc/emc_vmax_fast.py | 3 +- cinder/volume/drivers/emc/emc_vmax_fc.py | 4 +- cinder/volume/drivers/emc/emc_vmax_https.py | 24 +- cinder/volume/drivers/emc/emc_vmax_iscsi.py | 4 +- cinder/volume/drivers/emc/emc_vmax_masking.py | 17 +- .../volume/drivers/emc/emc_vmax_provision.py | 14 +- .../drivers/emc/emc_vmax_provision_v3.py | 111 +- cinder/volume/drivers/emc/emc_vmax_utils.py | 746 +++---- 10 files changed, 2076 insertions(+), 1094 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index 768f68bb0..4652eb889 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -231,15 +231,13 @@ class EMCVMAXCommonData(object): storage_system = 'SYMMETRIX+000195900551' storage_system_v3 = 'SYMMETRIX-+-000197200056' port_group = 'OS-portgroup-PG' - lunmaskctrl_id = \ - 'SYMMETRIX+000195900551+OS-fakehost-gold-MV' - lunmaskctrl_name = \ - 'OS-fakehost-gold-MV' - - initiatorgroup_id = \ - 'SYMMETRIX+000195900551+OS-fakehost-IG' - initiatorgroup_name = \ - 'OS-fakehost-IG' + lunmaskctrl_id = ( + 'SYMMETRIX+000195900551+OS-fakehost-gold-MV') + lunmaskctrl_name = 'OS-fakehost-gold-MV' + + initiatorgroup_id = ( + 'SYMMETRIX+000195900551+OS-fakehost-IG') + initiatorgroup_name = 'OS-fakehost-IG' initiatorgroup_creationclass = 'SE_InitiatorMaskingGroup' iscsi_initiator = 'iqn.1993-08.org.debian' storageextent_creationclass = 'CIM_StorageExtent' @@ -273,6 +271,9 @@ class EMCVMAXCommonData(object): totalmanagedspace_gbs = 931 subscribedcapacity_gbs = 466 fake_host = 'HostX@Backend#gold+1234567891011' + fake_host_v3 = 'HostX@Backend#Bronze+SRP_1+1234567891011' + fake_host_2_v3 = 'HostY@Backend#SRP_1+1234567891011' + unit_creationclass = 'CIM_ProtocolControllerForUnit' storage_type = 'gold' keybindings = {'CreationClassName': u'Symm_StorageVolume', @@ -288,11 +289,13 @@ class EMCVMAXCommonData(object): 'keybindings': keybindings} provider_location2 = {'classname': 'Symm_StorageVolume', 'keybindings': keybindings2} + provider_location_multi_pool = {'classname': 'Symm_StorageVolume', + 'keybindings': keybindings, + 'version': '2.2.0'} properties = {'ConsumableBlocks': '12345', 'BlockSize': '512'} - block_size = 512 test_volume = {'name': 'vol1', 'size': 1, 'volume_name': 'vol1', @@ -305,9 +308,9 @@ class EMCVMAXCommonData(object): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', - 'host': 'fake-host', + 'host': fake_host, 'NumberOfBlocks': 100, - 'BlockSize': block_size + 'BlockSize': 512 } test_volume_v2 = {'name': 'vol1', @@ -322,9 +325,9 @@ class EMCVMAXCommonData(object): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', - 'host': 'fake-host', + 'host': fake_host, 'NumberOfBlocks': 100, - 'BlockSize': block_size + 'BlockSize': 512 } test_volume_v3 = {'name': 'vol1', @@ -339,19 +342,11 @@ class EMCVMAXCommonData(object): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', - 'host': 'fake-host', + 'host': fake_host_v3, 'NumberOfBlocks': 100, - 'BlockSize': block_size + 'BlockSize': 512 } - 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, @@ -366,9 +361,26 @@ class EMCVMAXCommonData(object): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', - 'host': 'fake-host' + 'host': fake_host } + test_volume_CG_v3 = {'name': 'volInCG', + 'consistencygroup_id': 'abc', + 'size': 1, + 'volume_name': 'volInCG', + 'id': 'volInCG', + 'device_id': '1', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'volInCG', + 'display_description': + 'test volume in Consistency group', + 'volume_type_id': 'abc', + 'provider_location': + six.text_type(provider_location), + 'status': 'available', + 'host': fake_host_v3} + test_failed_volume = {'name': 'failed_vol', 'size': 1, 'volume_name': 'failed_vol', @@ -378,7 +390,8 @@ class EMCVMAXCommonData(object): 'project_id': 'project', 'display_name': 'failed_vol', 'display_description': 'test failed volume', - 'volume_type_id': 'abc'} + 'volume_type_id': 'abc', + 'host': fake_host} failed_delete_vol = {'name': 'failed_delete_vol', 'size': '-1', @@ -390,8 +403,9 @@ class EMCVMAXCommonData(object): 'display_name': 'failed delete vol', 'display_description': 'failed delete volume', 'volume_type_id': 'abc', - 'provider_location': six.text_type(provider_location2) - } + 'provider_location': + six.text_type(provider_location2), + 'host': fake_host} test_source_volume = {'size': 1, 'volume_type_id': 'sourceid', @@ -403,19 +417,35 @@ class EMCVMAXCommonData(object): 'provider_auth': None, 'project_id': 'project', 'id': '2', + 'host': fake_host, 'provider_location': six.text_type(provider_location), 'display_description': 'snapshot source volume'} + test_source_volume_v3 = {'size': 1, + 'volume_type_id': 'sourceid', + 'display_name': 'sourceVolume', + 'name': 'sourceVolume', + 'id': 'sourceVolume', + 'device_id': '1', + 'volume_name': 'vmax-154326', + 'provider_auth': None, + 'project_id': + 'project', 'id': '2', + 'host': fake_host_v3, + 'provider_location': + six.text_type(provider_location), + 'display_description': 'snapshot source volume'} + test_CG = {'name': 'myCG1', 'id': '12345abcde', 'volume_type_id': 'abc', - 'status': 'available', - 'host': 'fake-host' + 'status': 'available' } test_snapshot = {'name': 'myCG1', 'id': '12345abcde', - 'status': 'available' + 'status': 'available', + 'host': fake_host } test_CG_snapshot = {'name': 'testSnap', 'id': '12345abcde', @@ -425,13 +455,12 @@ class EMCVMAXCommonData(object): } location_info = {'location_info': '000195900551#silver#None', 'storage_protocol': 'ISCSI'} + location_info_v3 = {'location_info': '1234567891011#SRP_1#Bronze#DSS', + 'storage_protocol': 'FC'} test_host = {'capabilities': location_info, 'host': 'fake_host'} - - location_info_v3 = {'location_info': '0123456789#SRP_1#Bronze#DSS', - 'storage_protocol': 'FC'} test_host_v3 = {'capabilities': location_info_v3, - 'host': 'fake_v3_host'} + 'host': fake_host_2_v3} initiatorNames = ["123456789012345", "123456789054321"] test_ctxt = {} new_type = {} @@ -440,11 +469,10 @@ class EMCVMAXCommonData(object): 'volume_backend_name': 'V3_BE', 'storagetype:workload': u'DSS', 'storagetype:slo': u'Bronze', - 'storagetype:array': u'0123456789', - 'isV3': True} - majorVersion = 1 - minorVersion = 2 - revNumber = 3 + 'storagetype:array': u'1234567891011', + 'isV3': True, + 'portgroupname': u'OS-portgroup-PG'} + remainingSLOCapacity = '123456789' class FakeLookupService(object): @@ -476,8 +504,7 @@ 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, - WaitForCopyState=None): + ProtocolController=None, StorageID=None, IDType=None): rc = 0 myjob = SE_ConcreteJob() @@ -486,8 +513,8 @@ class FakeEcomConnection(object): myjob['status'] = 'success' myjob['type'] = ElementName - if Size == -1073741824 and \ - MethodName == 'CreateOrModifyCompositeElement': + if Size == -1073741824 and ( + MethodName == 'CreateOrModifyCompositeElement'): rc = 0 myjob = SE_ConcreteJob() myjob.classname = 'SE_ConcreteJob' @@ -495,14 +522,13 @@ class FakeEcomConnection(object): myjob['status'] = 'success' myjob['type'] = 'failed_delete_vol' - if ElementName == 'failed_vol' and \ - MethodName == 'CreateOrModifyElementFromStoragePool': + if ElementName == 'failed_vol' and ( + MethodName == 'CreateOrModifyElementFromStoragePool'): rc = 10 myjob['status'] = 'failure' - elif TheElements and \ - TheElements[0]['DeviceID'] == '99999' and \ - MethodName == 'ReturnElementsToStoragePool': + elif TheElements and TheElements[0]['DeviceID'] == '99999' and ( + MethodName == 'EMCReturnToStoragePool'): rc = 10 myjob['status'] = 'failure' elif HardwareId: @@ -519,8 +545,8 @@ class FakeEcomConnection(object): endpoints.append(endpoint2) targetendpoints['TargetEndpoints'] = endpoints return rc, targetendpoints - elif ReplicationType and \ - MethodName == 'GetDefaultReplicationSettingData': + elif ReplicationType and ( + MethodName == 'GetDefaultReplicationSettingData'): rc = 0 rsd = SE_ReplicationSettingData() rsd['DefaultInstance'] = SE_ReplicationSettingData() @@ -530,12 +556,11 @@ class FakeEcomConnection(object): rc = 0 ret['HardwareID'] = self.data.iscsi_initiator return rc, ret - elif MethodName == 'GetCompositeElements': + if MethodName == 'GetSupportedSizeRange': ret = {} rc = 0 - ret['OutElements'] = [self.data.metaHead_volume, - self.data.meta_volume1, - self.data.meta_volume2] + ret['EMCInformationSource'] = 3 + ret['EMCRemainingSLOCapacity'] = self.data.remainingSLOCapacity return rc, ret job = {'Job': myjob} @@ -579,8 +604,6 @@ 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 @@ -591,8 +614,6 @@ 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 @@ -982,7 +1003,7 @@ class FakeEcomConnection(object): def _getinstance_pool(self, objectpath): pool = {} pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' - pool['ElementName'] = self.data.poolname + pool['ElementName'] = 'gold' pool['SystemName'] = self.data.storage_system pool['TotalManagedSpace'] = self.data.totalmanagedspace_bits pool['EMCSubscribedCapacity'] = self.data.subscribedcapacity_bits @@ -999,7 +1020,6 @@ class FakeEcomConnection(object): srpstoragepool = SYMM_SrpStoragePool() srpstoragepool['CreationClassName'] = ( self.data.srpstoragepool_creationclass) - srpstoragepool['ElementName'] = 'SRP_1' classcimproperty = Fake_CIMProperty() totalManagedSpace = ( @@ -1093,28 +1113,38 @@ class FakeEcomConnection(object): def _enum_stconfsvcs(self): conf_services = [] - conf_service = {} - conf_service['SystemName'] = self.data.storage_system - conf_service['CreationClassName'] = \ - self.data.stconf_service_creationclass - conf_services.append(conf_service) + conf_service1 = {} + conf_service1['SystemName'] = self.data.storage_system + conf_service1['CreationClassName'] = ( + self.data.stconf_service_creationclass) + conf_services.append(conf_service1) + conf_service2 = {} + conf_service2['SystemName'] = self.data.storage_system_v3 + conf_service2['CreationClassName'] = ( + self.data.stconf_service_creationclass) + conf_services.append(conf_service2) return conf_services def _enum_ctrlconfsvcs(self): conf_services = [] conf_service = {} conf_service['SystemName'] = self.data.storage_system - conf_service['CreationClassName'] = \ - self.data.ctrlconf_service_creationclass + conf_service['CreationClassName'] = ( + self.data.ctrlconf_service_creationclass) conf_services.append(conf_service) + conf_service1 = {} + conf_service1['SystemName'] = self.data.storage_system_v3 + conf_service1['CreationClassName'] = ( + self.data.ctrlconf_service_creationclass) + conf_services.append(conf_service1) return conf_services def _enum_elemcompsvcs(self): comp_services = [] comp_service = {} comp_service['SystemName'] = self.data.storage_system - comp_service['CreationClassName'] = \ - self.data.elementcomp_service_creationclass + comp_service['CreationClassName'] = ( + self.data.elementcomp_service_creationclass) comp_services.append(comp_service) return comp_services @@ -1122,8 +1152,8 @@ class FakeEcomConnection(object): reloc_services = [] reloc_service = {} reloc_service['SystemName'] = self.data.storage_system - reloc_service['CreationClassName'] = \ - self.data.storreloc_service_creationclass + reloc_service['CreationClassName'] = ( + self.data.storreloc_service_creationclass) reloc_services.append(reloc_service) return reloc_services @@ -1131,8 +1161,8 @@ class FakeEcomConnection(object): replic_services = [] replic_service = {} replic_service['SystemName'] = self.data.storage_system - replic_service['CreationClassName'] = \ - self.data.replication_service_creationclass + replic_service['CreationClassName'] = ( + self.data.replication_service_creationclass) replic_services.append(replic_service) replic_service2 = {} replic_service2['SystemName'] = self.data.storage_system_v3 @@ -1144,8 +1174,8 @@ class FakeEcomConnection(object): def _enum_pools(self): pools = [] pool = {} - pool['InstanceID'] = self.data.storage_system + '+U+' + \ - self.data.storage_type + pool['InstanceID'] = ( + self.data.storage_system + '+U+' + self.data.storage_type) pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' pool['ElementName'] = 'gold' pools.append(pool) @@ -1154,8 +1184,8 @@ class FakeEcomConnection(object): def _enum_pool_details(self): pools = [] pool = {} - pool['InstanceID'] = self.data.storage_system + '+U+' + \ - self.data.storage_type + pool['InstanceID'] = ( + self.data.storage_system + '+U+' + self.data.storage_type) pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' pool['TotalManagedSpace'] = 12345678 pool['RemainingManagedSpace'] = 123456 @@ -1213,8 +1243,8 @@ class FakeEcomConnection(object): # Added vol to vol.path failed_delete_vol['SystemCreationClassName'] = 'Symm_StorageSystem' failed_delete_vol.path = failed_delete_vol - failed_delete_vol.path.classname = \ - failed_delete_vol['CreationClassName'] + failed_delete_vol.path.classname = ( + failed_delete_vol['CreationClassName']) vols.append(failed_delete_vol) failed_vol = EMC_StorageVolume() @@ -1226,8 +1256,7 @@ class FakeEcomConnection(object): # Added vol to vol.path failed_vol['SystemCreationClassName'] = 'Symm_StorageSystem' failed_vol.path = failed_vol - failed_vol.path.classname = \ - failed_vol['CreationClassName'] + failed_vol.path.classname = failed_vol['CreationClassName'] name_failed = {} name_failed['classname'] = 'Symm_StorageVolume' @@ -1241,31 +1270,6 @@ 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): @@ -1516,15 +1520,6 @@ 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 = {} @@ -1541,7 +1536,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.tempdir = tempfile.mkdtemp() super(EMCVMAXISCSIDriverNoFastTestCase, self).setUp() self.config_file_path = None - self.config_file_1364232 = None self.create_fake_config_file_no_fast() self.addCleanup(self._cleanup) @@ -1581,19 +1575,20 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): doc.writexml(f) f.close() - def create_fake_config_file_no_fast_with_add_ons(self): + def create_fake_config_file_no_fast_with_interval_retries(self): doc = minidom.Document() emc = doc.createElement("EMC") doc.appendChild(emc) doc = self.add_array_info(doc, emc) doc = self.add_interval_and_retries(doc, emc) - filename = 'cinder_emc_config_ISCSINoFAST.xml' - self.config_file_path = self.tempdir + '/' + filename + filename = 'cinder_emc_config_ISCSINoFAST_int_ret.xml' + config_file_path = self.tempdir + '/' + filename f = open(self.config_file_path, 'w') doc.writexml(f) f.close() + return config_file_path def create_fake_config_file_no_fast_with_interval(self): @@ -1602,12 +1597,13 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): doc.appendChild(emc) doc = self.add_array_info(doc, emc) doc = self.add_interval_only(doc, emc) - filename = 'cinder_emc_config_ISCSINoFAST.xml' - self.config_file_path = self.tempdir + '/' + filename + filename = 'cinder_emc_config_ISCSINoFAST_int.xml' + config_file_path = self.tempdir + '/' + filename f = open(self.config_file_path, 'w') doc.writexml(f) f.close() + return config_file_path def create_fake_config_file_no_fast_with_retries(self): @@ -1616,12 +1612,13 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): doc.appendChild(emc) doc = self.add_array_info(doc, emc) doc = self.add_retries_only(doc, emc) - filename = 'cinder_emc_config_ISCSINoFAST.xml' - self.config_file_path = self.tempdir + '/' + filename + filename = 'cinder_emc_config_ISCSINoFAST_ret.xml' + config_file_path = self.tempdir + '/' + filename f = open(self.config_file_path, 'w') doc.writexml(f) f.close() + return config_file_path def add_array_info(self, doc, emc): array = doc.createElement("Array") @@ -1663,7 +1660,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): pool.appendChild(pooltext) array = doc.createElement("Array") - arraytext = doc.createTextNode("0123456789") + arraytext = doc.createTextNode("1234567891011") emc.appendChild(array) array.appendChild(arraytext) @@ -1702,8 +1699,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): # fix for https://bugs.launchpad.net/cinder/+bug/1364232 def create_fake_config_file_1364232(self): filename = 'cinder_emc_config_1364232.xml' - self.config_file_1364232 = self.tempdir + '/' + filename - text_file = open(self.config_file_1364232, "w") + config_file_1364232 = self.tempdir + '/' + filename + text_file = open(config_file_1364232, "w") text_file.write("\n\n" "10.10.10.10\n" "5988\n" @@ -1719,6 +1716,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): "\nSILVER1\n" "") text_file.close() + return config_file_1364232 def fake_ecom_connection(self): conn = FakeEcomConnection() @@ -1750,39 +1748,18 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertEqual(storageHardwareIDInstanceNames[0], self.data.iscsi_initiator) - 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_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_hardware_type(self): iqn_initiator = 'iqn.1992-04.com.emc: 50000973f006dd80' @@ -1916,6 +1893,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_wait_for_sync(self): mysync = 'fakesync' conn = self.fake_ecom_connection() + self.driver.utils._is_sync_complete = mock.Mock( return_value=True) rc = self.driver.utils.wait_for_sync(conn, mysync) @@ -1940,11 +1918,15 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_wait_for_sync_extra_specs(self): mysync = 'fakesync' conn = self.fake_ecom_connection() - self.create_fake_config_file_no_fast_with_add_ons() + file_name = ( + self.create_fake_config_file_no_fast_with_interval_retries()) extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + extraSpecs = self.driver.common._set_v2_extra_specs(extraSpecs, + poolRec) self.driver.utils._is_sync_complete = mock.Mock( return_value=True) @@ -1970,6 +1952,9 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): mock.ANY) loopingcall.FixedIntervalLoopingCall.reset_mock() loopingcall.FixedIntervalLoopingCall = loopingcall_orig + bExists = os.path.exists(file_name) + if bExists: + os.remove(file_name) # Bug 1395830: _find_lun throws exception when lun is not found. def test_find_lun(self): @@ -1999,19 +1984,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): volume2 = EMC_StorageVolume() volume2['name'] = 'myVol' volume2['provider_location'] = six.text_type(provider_location2) - verify_orig = self.driver.common.conn.GetInstance - self.driver.common.conn.GetInstance = mock.Mock( + verify_orig = self.driver.common.utils.get_existing_instance + self.driver.common.utils.get_existing_instance = mock.Mock( return_value=None) findlun2 = self.driver.common._find_lun(volume2) # Not found. self.assertIsNone(findlun2) - self.driver.utils.get_instance_name( + instancename2 = self.driver.utils.get_instance_name( provider_location2['classname'], keybindings2) - self.driver.common.conn.GetInstance.assert_called_once_with( - keybindings2) - self.driver.common.conn.GetInstance.reset_mock() - self.driver.common.conn.GetInstance = verify_orig + 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 keybindings3 = {'CreationClassName': u'Symm_StorageVolume', 'SystemName': u'SYMMETRIX+000195900551', @@ -2031,9 +2016,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): # Bug 1403160 - make sure the masking view is cleanly deleted def test_last_volume_delete_masking_view(self): extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) conn = self.fake_ecom_connection() controllerConfigService = ( self.driver.utils.find_controller_configuration_service( @@ -2074,10 +2056,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): volumeInstanceName = ( conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) volumeName = "1403160-Vol" - extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + extraSpecs = {'volume_backend_name': 'GOLD_BE', + 'isV3': False} # Deleting Storage Group failed self.assertRaises( @@ -2372,41 +2352,49 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertIsNone(foundPoolInstanceName2) def test_get_volume_stats_1364232(self): - self.create_fake_config_file_1364232() + file_name = self.create_fake_config_file_1364232() + + arrayInfo = self.driver.utils.parse_file_to_get_array_map(file_name) self.assertEqual( - '000198700439', - self.driver.utils.parse_array_name_from_file( - self.config_file_1364232)) + '000198700439', arrayInfo[0]['SerialNumber']) self.assertEqual( - 'FC_SLVR1', - self.driver.utils.parse_pool_name_from_file( - self.config_file_1364232)) + 'FC_SLVR1', arrayInfo[0]['PoolName']) self.assertEqual( - 'SILVER1', - self.driver.utils.parse_fast_policy_name_from_file( - self.config_file_1364232)) + 'SILVER1', arrayInfo[0]['FastPolicy']) self.assertTrue( - 'OS-PORTGROUP' in - self.driver.utils.parse_file_to_get_port_group_name( - self.config_file_1364232)) + 'OS-PORTGROUP' in arrayInfo[0]['PortGroup']) + bExists = os.path.exists(file_name) + if bExists: + os.remove(file_name) def test_intervals_and_retries_override( self): - self.create_fake_config_file_no_fast_with_add_ons() + file_name = ( + self.create_fake_config_file_no_fast_with_interval_retries()) extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + extraSpecs = self.driver.common._set_v2_extra_specs(extraSpecs, + poolRec) self.assertEqual(40, self.driver.utils._get_max_job_retries(extraSpecs)) self.assertEqual(5, self.driver.utils._get_interval_in_secs(extraSpecs)) + bExists = os.path.exists(file_name) + if bExists: + os.remove(file_name) + def test_intervals_and_retries_default(self): extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + extraSpecs = self.driver.common._set_v2_extra_specs(extraSpecs, + poolRec) self.assertEqual(60, self.driver.utils._get_max_job_retries(extraSpecs)) self.assertEqual(10, @@ -2414,34 +2402,44 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_interval_only(self): extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - self.create_fake_config_file_no_fast_with_interval() - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + file_name = self.create_fake_config_file_no_fast_with_interval() + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + extraSpecs = self.driver.common._set_v2_extra_specs(extraSpecs, + poolRec) self.assertEqual(60, self.driver.utils._get_max_job_retries(extraSpecs)) self.assertEqual(20, self.driver.utils._get_interval_in_secs(extraSpecs)) + bExists = os.path.exists(file_name) + if bExists: + os.remove(file_name) + def test_retries_only(self): extraSpecs = {'volume_backend_name': 'ISCSINoFAST'} - self.create_fake_config_file_no_fast_with_retries() - extraSpecs = ( - self.driver.common._get_job_extra_specs(self.config_file_path, - extraSpecs)) + file_name = self.create_fake_config_file_no_fast_with_retries() + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + extraSpecs = self.driver.common._set_v2_extra_specs(extraSpecs, + poolRec) self.assertEqual(70, self.driver.utils._get_max_job_retries(extraSpecs)) self.assertEqual(10, self.driver.utils._get_interval_in_secs(extraSpecs)) + bExists = os.path.exists(file_name) + if bExists: + os.remove(file_name) + @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', return_value=False) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_array_name_from_file', - return_value="123456789") @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'get_pool_capacities', @@ -2458,7 +2456,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): mock_storage_system, mock_is_fast_enabled, mock_capacity, - mock_array, mock_is_v3): self.driver.get_volume_stats(True) @@ -2530,14 +2527,15 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): notfound_delete_vol['SystemCreationClassName'] = 'Symm_StorageSystem' notfound_delete_vol['volume_type_id'] = 'abc' notfound_delete_vol['provider_location'] = None + notfound_delete_vol['host'] = self.data.fake_host name = {} name['classname'] = 'Symm_StorageVolume' keys = {} keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] = \ - notfound_delete_vol['SystemCreationClassName'] + keys['SystemCreationClassName'] = ( + notfound_delete_vol['SystemCreationClassName']) name['keybindings'] = keys self.driver.delete_volume(notfound_delete_vol) @@ -2735,7 +2733,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -2770,7 +2768,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -2831,7 +2829,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( FakeDB, @@ -3042,7 +3040,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3153,83 +3151,11 @@ 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) if bExists: os.remove(self.config_file_path) - if self.config_file_1364232: - bExists = os.path.exists(self.config_file_1364232) - if bExists: - os.remove(self.config_file_1364232) shutil.rmtree(self.tempdir) @@ -3316,11 +3242,6 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): emc.appendChild(pool) pool.appendChild(pooltext) - array = doc.createElement("Array") - arraytext = doc.createTextNode("0123456789") - emc.appendChild(array) - array.appendChild(arraytext) - portgroups = doc.createElement("PortGroups") portgroups.appendChild(portgroup) emc.appendChild(portgroups) @@ -3349,10 +3270,6 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_array_name_from_file', - return_value="123456789") @mock.patch.object( emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', @@ -3373,8 +3290,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): mock_storage_system, mock_is_fast_enabled, mock_get_policy, - mock_capacity, - mock_array): + mock_capacity): self.driver.get_volume_stats(True) @mock.patch.object( @@ -3460,14 +3376,15 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): notfound_delete_vol['SystemName'] = self.data.storage_system notfound_delete_vol['DeviceID'] = notfound_delete_vol['id'] notfound_delete_vol['SystemCreationClassName'] = 'Symm_StorageSystem' + notfound_delete_vol['host'] = self.data.fake_host name = {} name['classname'] = 'Symm_StorageVolume' keys = {} keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] = \ - notfound_delete_vol['SystemCreationClassName'] + keys['SystemCreationClassName'] = ( + notfound_delete_vol['SystemCreationClassName']) name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' notfound_delete_vol['provider_location'] = None @@ -3595,7 +3512,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3633,7 +3550,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3683,7 +3600,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -3953,11 +3870,6 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): emc.appendChild(pool) pool.appendChild(pooltext) - array = doc.createElement("Array") - arraytext = doc.createTextNode("0123456789") - emc.appendChild(array) - array.appendChild(arraytext) - timeout = doc.createElement("Timeout") timeouttext = doc.createTextNode("0") emc.appendChild(timeout) @@ -3981,14 +3893,6 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'isArrayV3', - return_value=False) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_array_name_from_file', - return_value="123456789") @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'get_pool_capacities', @@ -4004,9 +3908,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): def test_get_volume_stats_no_fast(self, mock_storage_system, mock_is_fast_enabled, - mock_capacity, - mock_array, - mock_is_v3): + mock_capacity): self.driver.get_volume_stats(True) @mock.patch.object( @@ -4075,14 +3977,15 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): notfound_delete_vol['SystemName'] = self.data.storage_system notfound_delete_vol['DeviceID'] = notfound_delete_vol['id'] notfound_delete_vol['SystemCreationClassName'] = 'Symm_StorageSystem' + notfound_delete_vol['host'] = self.data.fake_host name = {} name['classname'] = 'Symm_StorageVolume' keys = {} keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] = \ - notfound_delete_vol['SystemCreationClassName'] + keys['SystemCreationClassName'] = ( + notfound_delete_vol['SystemCreationClassName']) name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' notfound_delete_vol['provider_location'] = None @@ -4312,38 +4215,6 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): self.driver.delete_cgsnapshot( self.data.test_ctxt, self.data.test_CG_snapshot) - def create_fake_config_file_parse_port_group(self): - filename = 'cinder_emc_config_file_parse_port_group.xml' - self.config_file_parse_port_group = self.tempdir + '/' + filename - text_file = open(self.config_file_parse_port_group, "w") - text_file.write( - "\n\n" - "10.108.246.202\n" - "5988\n" - "admin\t\n" - "#1Password\n" - "OS-PORTGROUP1-PG\r\n" - "\n" - "\n" - " \n" - "\n" - "\n000198700439" - " \n\nFC_SLVR1\n" - "\nSILVER1\n" - "") - text_file.close() - - def test_get_port_group_parser(self): - self.create_fake_config_file_parse_port_group() - for _var in range(0, 10): - self.assertEqual( - u'OS-PORTGROUP1-PG', - self.driver.utils.parse_file_to_get_port_group_name( - self.config_file_parse_port_group)) - bExists = os.path.exists(self.config_file_parse_port_group) - if bExists: - os.remove(self.config_file_parse_port_group) - def test_manage_existing_get_size(self): volume = {} metadata = {'key': 'array', @@ -4430,7 +4301,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'status': 'available', 'host': self.data.fake_host, 'NumberOfBlocks': 100, - 'BlockSize': self.data.block_size + 'BlockSize': 512 } common = self.driver.common common._initial_setup = mock.Mock( @@ -4463,7 +4334,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'status': 'available', 'host': self.data.fake_host, 'NumberOfBlocks': 100, - 'BlockSize': self.data.block_size + 'BlockSize': 512 } common = self.driver.common common._initial_setup = mock.Mock( @@ -4556,7 +4427,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): pool.appendChild(pooltext) array = doc.createElement("Array") - arraytext = doc.createTextNode("0123456789") + arraytext = doc.createTextNode("1234567891011") emc.appendChild(array) array.appendChild(arraytext) @@ -4587,10 +4458,6 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_array_name_from_file', - return_value="123456789") @mock.patch.object( emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', @@ -4611,8 +4478,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): mock_storage_system, mock_is_fast_enabled, mock_get_policy, - mock_capacity, - mock_array): + mock_capacity): self.driver.get_volume_stats(True) @mock.patch.object( @@ -4694,14 +4560,15 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): notfound_delete_vol['SystemName'] = self.data.storage_system notfound_delete_vol['DeviceID'] = notfound_delete_vol['id'] notfound_delete_vol['SystemCreationClassName'] = 'Symm_StorageSystem' + notfound_delete_vol['host'] = self.data.fake_host name = {} name['classname'] = 'Symm_StorageVolume' keys = {} keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] = \ - notfound_delete_vol['SystemCreationClassName'] + keys['SystemCreationClassName'] = ( + notfound_delete_vol['SystemCreationClassName']) name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' notfound_delete_vol['provider_location'] = None @@ -4846,7 +4713,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -4876,7 +4743,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): mock.Mock(return_value=True)) self.driver.create_snapshot(self.data.test_volume) - def test_create_snapshot_fast_failed(self): + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_validate_pool', + return_value=('Bogus_Pool')) + def test_create_snapshot_fast_failed(self, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_snapshot, @@ -4884,7 +4755,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -4953,7 +4824,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, - 'get_meta_members_capacity_in_byte', + 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, @@ -5146,9 +5017,11 @@ class EMCV3DriverTestCase(test.TestCase): self.tempdir = tempfile.mkdtemp() super(EMCV3DriverTestCase, self).setUp() self.config_file_path = None - self.create_fake_config_file_fast() + self.create_fake_config_file_v3() self.addCleanup(self._cleanup) + self.set_configuration() + def set_configuration(self): configuration = mock.Mock() configuration.cinder_emc_config_file = self.config_file_path configuration.safe_get.return_value = 'V3' @@ -5168,7 +5041,7 @@ class EMCV3DriverTestCase(test.TestCase): driver.db = FakeDB() self.driver = driver - def create_fake_config_file_fast(self): + def create_fake_config_file_v3(self): doc = minidom.Document() emc = doc.createElement("EMC") @@ -5204,7 +5077,7 @@ class EMCV3DriverTestCase(test.TestCase): pool.appendChild(pooltext) array = doc.createElement("Array") - arraytext = doc.createTextNode("0123456789") + arraytext = doc.createTextNode("1234567891011") emc.appendChild(array) array.appendChild(arraytext) @@ -5245,27 +5118,39 @@ class EMCV3DriverTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return True + def default_extraspec(self): + return {'storagetype:pool': 'SRP_1', + 'volume_backend_name': 'V3_BE', + 'storagetype:workload': 'DSS', + 'storagetype:slo': 'Bronze', + 'storagetype:array': '1234567891011', + 'isV3': True, + 'portgroupname': 'OS-portgroup-PG'} + + def test_initial_setup(self): + self.driver.common._register_config_file_from_config_group = ( + mock.Mock(return_value=self.config_file_path)) + extraSpecs = ( + self.driver.common._initial_setup(self.data.test_volume_v3)) + self.assertEqual('SRP_1', extraSpecs['storagetype:pool']) + self.assertEqual('DSS', extraSpecs['storagetype:workload']) + self.assertEqual('Bronze', extraSpecs['storagetype:slo']) + self.assertEqual('1234567891011', extraSpecs['storagetype:array']) + self.assertEqual('OS-portgroup-PG', extraSpecs['portgroupname']) + self.assertTrue(extraSpecs['isV3']) + @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', return_value=True) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_array_name_from_file', - return_value="123456789") @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value=None) def test_get_volume_stats_v3( - self, mock_storage_system, mock_array, mock_is_v3): + self, mock_storage_system, mock_is_v3): self.driver.get_volume_stats(True) - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5275,18 +5160,12 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_create_volume_v3_success( - self, _mock_volume_type, mock_storage_system, mock_range): + self, _mock_volume_type, mock_storage_system): + self.data.test_volume_v3['host'] = self.data.fake_host_v3 + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) self.driver.create_volume(self.data.test_volume_v3) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_slo_from_file', - return_value='NONE') - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5296,19 +5175,21 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_create_volume_v3_no_slo_success( - self, _mock_volume_type, mock_storage_system, - mock_range, mock_slo): - self.driver.create_volume(self.data.test_volume_v3) + self, _mock_volume_type, mock_storage_system): + v3_vol = self.data.test_volume_v3 + v3_vol['host'] = 'HostX@Backend#NONE+SRP_1+1234567891011' + extraSpecs = {'storagetype:pool': 'SRP_1', + 'volume_backend_name': 'V3_BE', + 'storagetype:workload': 'DSS', + 'storagetype:slo': 'NONE', + 'storagetype:array': '1234567891011', + 'isV3': True, + 'portgroupname': 'OS-portgroup-PG'} + self.driver.common._initial_setup = mock.Mock( + return_value=extraSpecs) + + self.driver.create_volume(v3_vol) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_slo_from_file', - return_value='Bogus') - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5318,17 +5199,21 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_create_volume_v3_invalid_slo_failed( - self, _mock_volume_type, mock_storage_system, - mock_range, mock_slo): + self, _mock_volume_type, mock_storage_system): + extraSpecs = {'storagetype:pool': 'SRP_1', + 'volume_backend_name': 'V3_BE', + 'storagetype:workload': 'DSS', + 'storagetype:slo': 'Bogus', + 'storagetype:array': '1234567891011', + 'isV3': True, + 'portgroupname': 'OS-portgroup-PG'} + self.driver.common._initial_setup = mock.Mock( + return_value=extraSpecs) + self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, self.data.test_volume) - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5338,21 +5223,20 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_create_volume_in_CG_v3_success( - self, _mock_volume_type, mock_storage_system, mock_range): - self.driver.create_volume(self.data.test_volume_CG) + self, _mock_volume_type, mock_storage_system): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_volume(self.data.test_volume_CG_v3) @mock.patch.object( volume_types, 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_delete_volume_v3_success(self, _mock_volume_type): - self.driver.delete_volume(self.data.test_volume) + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.delete_volume(self.data.test_volume_v3) - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5364,29 +5248,28 @@ class EMCV3DriverTestCase(test.TestCase): @mock.patch.object( FakeDB, 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) + return_value=EMCVMAXCommonData.test_source_volume_v3) def test_create_snapshot_v3_success( - self, mock_volume_db, mock_type, moke_pool, mock_siz): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_snapshot(self.data.test_volume) + self, mock_volume_db, mock_type, moke_pool): + self.data.test_volume_v3['volume_name'] = "vmax-1234567" + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_snapshot(self.data.test_volume_v3) @mock.patch.object( FakeDB, 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) + return_value=EMCVMAXCommonData.test_source_volume_v3) @mock.patch.object( volume_types, 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_delete_snapshot_v3_success(self, mock_volume_type, mock_db): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.delete_snapshot(self.data.test_volume) + self.data.test_volume_v3['volume_name'] = "vmax-1234567" + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.delete_snapshot(self.data.test_volume_v3) - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5400,8 +5283,8 @@ class EMCV3DriverTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) def test_create_cloned_volume_v3_success( - self, mock_volume_db, mock_type, moke_pool, mock_size): - self.data.test_volume['volume_name'] = "vmax-1234567" + self, mock_volume_db, mock_type, moke_pool): + self.data.test_volume_v3['volume_name'] = "vmax-1234567" cloneVol = {} cloneVol['name'] = 'vol1' cloneVol['id'] = '10' @@ -5412,8 +5295,11 @@ class EMCV3DriverTestCase(test.TestCase): cloneVol['volume_type_id'] = 'abc' cloneVol['provider_location'] = None cloneVol['NumberOfBlocks'] = 100 - cloneVol['BlockSize'] = self.data.block_size - self.driver.create_cloned_volume(cloneVol, self.data.test_volume) + cloneVol['BlockSize'] = 512 + cloneVol['host'] = self.data.fake_host_v3 + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_cloned_volume(cloneVol, self.data.test_volume_v3) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, @@ -5426,7 +5312,7 @@ class EMCV3DriverTestCase(test.TestCase): def test_create_CG_v3_success( self, _mock_volume_type, _mock_storage_system): self.driver.create_consistencygroup( - self.data.test_ctxt, self.data.test_CG) + self.data.test_ctxt, self.data.test_volume_CG_v3) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, @@ -5468,6 +5354,8 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_migrate_volume_v3_success(self, _mock_volume_type): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) self.driver.migrate_volume(self.data.test_ctxt, self.data.test_volume, self.data.test_host) @@ -5490,8 +5378,10 @@ class EMCV3DriverTestCase(test.TestCase): def test_retype_volume_v3_success( self, _mock_volume_type, mock_fast_settings, mock_storage_group, mock_found_SG): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) self.assertTrue(self.driver.retype( - self.data.test_ctxt, self.data.test_volume, self.data.new_type, + self.data.test_ctxt, self.data.test_volume_v3, self.data.new_type, self.data.diff, self.data.test_host_v3)) @mock.patch.object( @@ -5504,8 +5394,10 @@ class EMCV3DriverTestCase(test.TestCase): return_value={'volume_backend_name': 'V3_BE'}) def test_retype_volume_same_host_failure( self, _mock_volume_type, mock_fast_settings): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) self.assertFalse(self.driver.retype( - self.data.test_ctxt, self.data.test_volume, self.data.new_type, + self.data.test_ctxt, self.data.test_volume_v3, self.data.new_type, self.data.diff, self.data.test_host_v3)) @mock.patch.object( @@ -5623,8 +5515,10 @@ class EMCV3DriverTestCase(test.TestCase): common = self.driver.common common.get_target_wwns = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) data = self.driver.initialize_connection( - self.data.test_volume, self.data.connector) + self.data.test_volume_v3, self.data.connector) # Test the no lookup service, pre-zoned case. common.get_target_wwns.assert_called_once_with( EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) @@ -5640,6 +5534,8 @@ class EMCV3DriverTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'V3_BE'}) def test_map_v3_failed(self, _mock_volume_type, mock_wrap_device): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) self.assertRaises(exception.VolumeBackendAPIException, self.driver.initialize_connection, self.data.test_volume, @@ -5670,7 +5566,9 @@ class EMCV3DriverTestCase(test.TestCase): common = self.driver.common common.get_target_wwns = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) - data = self.driver.terminate_connection(self.data.test_volume, + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + data = self.driver.terminate_connection(self.data.test_volume_v3, self.data.connector) common.get_target_wwns.assert_called_once_with( EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) @@ -5678,11 +5576,6 @@ class EMCV3DriverTestCase(test.TestCase): self.assertEqual(numTargetWwns, len(data['data'])) # Bug https://bugs.launchpad.net/cinder/+bug/1440154 - @mock.patch.object( - emc_vmax_provision_v3.EMCVMAXProvisionV3, - '_get_supported_size_range_for_SLO', - return_value={'MaximumVolumeSize': '30000000000', - 'MinimumVolumeSize': '100000'}) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5694,7 +5587,7 @@ class EMCV3DriverTestCase(test.TestCase): @mock.patch.object( FakeDB, 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) + return_value=EMCVMAXCommonData.test_source_volume_v3) @mock.patch.object( emc_vmax_provision_v3.EMCVMAXProvisionV3, 'create_element_replica') @@ -5704,7 +5597,7 @@ class EMCV3DriverTestCase(test.TestCase): return_value=(None, None)) def test_create_clone_v3_assert_clean_up_target_volume( self, mock_sync, mock_create_replica, mock_volume_db, - mock_type, moke_pool, mock_size): + mock_type, moke_pool): self.data.test_volume['volume_name'] = "vmax-1234567" e = exception.VolumeBackendAPIException('CreateElementReplica Ex') common = self.driver.common @@ -5713,17 +5606,18 @@ class EMCV3DriverTestCase(test.TestCase): common._create_v3_volume = ( mock.Mock(return_value=(0, volumeDict, self.data.storage_system))) conn = self.fake_ecom_connection() + storageConfigService = [] storageConfigService = {} storageConfigService['SystemName'] = EMCVMAXCommonData.storage_system - storageConfigService['CreationClassName'] = \ - self.data.stconf_service_creationclass + storageConfigService['CreationClassName'] = ( + self.data.stconf_service_creationclass) common._delete_from_pool_v3 = mock.Mock(return_value=0) mock_create_replica.side_effect = e self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, - self.data.test_volume, - EMCVMAXCommonData.test_source_volume) - extraSpecs = common._initial_setup(self.data.test_volume) + self.data.test_volume_v3, + EMCVMAXCommonData.test_source_volume_v3) + extraSpecs = common._initial_setup(self.data.test_volume_v3) targetInstance = ( conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) storageGroupName = common.utils.get_v3_storage_group_name('SRP_1', @@ -5737,6 +5631,930 @@ class EMCV3DriverTestCase(test.TestCase): storageGroupName, extraSpecs) + def test_get_remaining_slo_capacity_wlp(self): + conn = self.fake_ecom_connection() + array_info = {'Workload': u'DSS', 'SLO': u'Bronze'} + storagesystem = self.data.storage_system_v3 + srpPoolInstanceName = {} + srpPoolInstanceName['InstanceID'] = ( + self.data.storage_system_v3 + '+U+' + 'SRP_1') + srpPoolInstanceName['CreationClassName'] = ( + 'Symm_VirtualProvisioningPool') + srpPoolInstanceName['ElementName'] = 'SRP_1' + + remainingCapacityGb = ( + self.driver.common.provisionv3._get_remaining_slo_capacity_wlp( + conn, srpPoolInstanceName, array_info, storagesystem)) + remainingSLOCapacityGb = self.driver.common.utils.convert_bits_to_gbs( + self.data.remainingSLOCapacity) + self.assertEqual(remainingSLOCapacityGb, remainingCapacityGb) + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) + + +class EMCV2MultiPoolDriverTestCase(test.TestCase): + + def setUp(self): + self.data = EMCVMAXCommonData() + self.vol_v2 = self.data.test_volume_v2 + self.vol_v2['provider_location'] = ( + six.text_type(self.data.provider_location_multi_pool)) + self.tempdir = tempfile.mkdtemp() + super(EMCV2MultiPoolDriverTestCase, self).setUp() + self.config_file_path = None + self.create_fake_config_file_multi_pool() + self.addCleanup(self._cleanup) + + configuration = mock.Mock() + configuration.safe_get.return_value = 'MULTI_POOL' + configuration.cinder_emc_config_file = self.config_file_path + configuration.config_group = 'MULTI_POOL' + + self.stubs.Set(emc_vmax_iscsi.EMCVMAXISCSIDriver, + 'smis_do_iscsi_discovery', + self.fake_do_iscsi_discovery) + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', + self.fake_ecom_connection) + instancename = FakeCIMInstanceName() + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', + instancename.fake_getinstancename) + self.stubs.Set(time, 'sleep', + self.fake_sleep) + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', + self.fake_is_v3) + + driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) + driver.db = FakeDB() + self.driver = driver + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) + + def create_fake_config_file_multi_pool(self): + doc = minidom.Document() + emc = doc.createElement("EMC") + doc.appendChild(emc) + + eComServers = doc.createElement("EcomServers") + emc.appendChild(eComServers) + + eComServer = doc.createElement("EcomServer") + eComServers.appendChild(eComServer) + + ecomserverip = doc.createElement("EcomServerIp") + eComServer.appendChild(ecomserverip) + ecomserveriptext = doc.createTextNode("1.1.1.1") + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + eComServer.appendChild(ecomserverport) + ecomserverporttext = doc.createTextNode("10") + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + eComServer.appendChild(ecomusername) + ecomusernametext = doc.createTextNode("user") + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + eComServer.appendChild(ecompassword) + ecompasswordtext = doc.createTextNode("pass") + ecompassword.appendChild(ecompasswordtext) + + arrays = doc.createElement("Arrays") + eComServer.appendChild(arrays) + + array = doc.createElement("Array") + arrays.appendChild(array) + + serialNo = doc.createElement("SerialNumber") + array.appendChild(serialNo) + serialNoText = doc.createTextNode("1234567891011") + serialNo.appendChild(serialNoText) + + portgroups = doc.createElement("PortGroups") + array.appendChild(portgroups) + + portgroup = doc.createElement("PortGroup") + portgroups.appendChild(portgroup) + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + pools = doc.createElement("Pools") + array.appendChild(pools) + + pool = doc.createElement("Pool") + pools.appendChild(pool) + poolName = doc.createElement("PoolName") + pool.appendChild(poolName) + poolNameText = doc.createTextNode("gold") + poolName.appendChild(poolNameText) + + pool2 = doc.createElement("Pool") + pools.appendChild(pool2) + pool2Name = doc.createElement("PoolName") + pool2.appendChild(pool2Name) + pool2NameText = doc.createTextNode("SATA_BRONZE1") + pool2Name.appendChild(pool2NameText) + pool2FastPolicy = doc.createElement("FastPolicy") + pool2.appendChild(pool2FastPolicy) + pool2FastPolicyText = doc.createTextNode("BRONZE1") + pool2FastPolicy.appendChild(pool2FastPolicyText) + + filename = 'cinder_emc_config_V2_MULTI_POOL.xml' + self.config_file_path = self.tempdir + '/' + filename + + f = open(self.config_file_path, 'w') + doc.writexml(f) + f.close() + + def fake_ecom_connection(self): + self.conn = FakeEcomConnection() + return self.conn + + def fake_do_iscsi_discovery(self, volume): + output = [] + item = '10.10.0.50: 3260,1 iqn.1992-04.com.emc: 50000973f006dd80' + output.append(item) + return output + + def fake_sleep(self, seconds): + return + + def fake_is_v3(self, conn, serialNumber): + return False + + def default_extraspec(self): + return {'storagetype:pool': u'gold', + 'volume_backend_name': 'MULTI_POOL_BE', + 'storagetype:fastpolicy': None, + 'storagetype:compositetype': u'concatenated', + 'storagetype:membercount': 1, + 'storagetype:array': u'1234567891011', + 'isV3': False, + 'portgroupname': u'OS-portgroup-PG'} + + def test_initial_setup(self): + self.driver.common._register_config_file_from_config_group = ( + mock.Mock(return_value=self.config_file_path)) + extraSpecs = self.driver.common._initial_setup(self.vol_v2) + self.assertEqual('gold', extraSpecs['storagetype:pool']) + self.assertEqual(None, extraSpecs['storagetype:fastpolicy']) + self.assertEqual('concatenated', + extraSpecs['storagetype:compositetype']) + self.assertEqual('1234567891011', extraSpecs['storagetype:array']) + self.assertEqual('OS-portgroup-PG', extraSpecs['portgroupname']) + self.assertFalse(extraSpecs['isV3']) + + def test_validate_pool(self): + v2_valid_pool = self.data.test_volume_v2.copy() + # Pool aware scheduler enabled + v2_valid_pool['host'] = self.data.fake_host + pool = self.driver.common._validate_pool(v2_valid_pool) + self.assertEqual('gold+1234567891011', pool) + + # Cannot get the pool from the host + v2_valid_pool['host'] = 'HostX@Backend' + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.common._validate_pool, + v2_valid_pool) + + # Legacy test. Provider Location does not have the version + v2_valid_pool['host'] = self.data.fake_host + v2_valid_pool['provider_location'] = self.data.provider_location + pool = self.driver.common._validate_pool(v2_valid_pool) + self.assertIsNone(pool) + + def test_array_info_multi_pool(self): + + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + self.assertTrue(len(arrayInfo) == 2) + for arrayInfoRec in arrayInfo: + self.assertEqual( + '1234567891011', arrayInfoRec['SerialNumber']) + self.assertTrue( + self.data.port_group in arrayInfoRec['PortGroup']) + self.assertTrue( + self.data.poolname in arrayInfoRec['PoolName'] or + 'SATA_BRONZE1' in arrayInfoRec['PoolName']) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_create_volume_multi_pool_success( + self, _mock_volume_type, mock_storage_system): + self.vol_v2['provider_location'] = None + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_volume(self.vol_v2) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_delete_volume_multi_pool_success( + self, _mock_volume_type, mock_storage_system): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.delete_volume(self.vol_v2) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_create_volume_in_CG_multi_pool_success( + self, _mock_volume_type, mock_storage_system): + self.data.test_volume_CG['provider_location'] = None + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_volume(self.data.test_volume_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_retype_volume_multi_pool_success( + self, _mock_volume_type): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.retype( + self.data.test_ctxt, self.vol_v2, self.data.new_type, + self.data.diff, self.data.test_host) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + # There is only one unique array in the conf file + def test_create_CG_multi_pool_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_delete_CG_no_volumes_multi_pool_success( + self, _mock_volume_type, _mock_storage_system, + _mock_db_volumes, _mock_members): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_POOL_BE'}) + def test_delete_CG_with_volumes_multi_pool_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) + + +class EMCV3MultiSloDriverTestCase(test.TestCase): + + def setUp(self): + self.data = EMCVMAXCommonData() + self.vol_v3 = self.data.test_volume_v3 + self.vol_v3['provider_location'] = ( + six.text_type(self.data.provider_location_multi_pool)) + + self.tempdir = tempfile.mkdtemp() + super(EMCV3MultiSloDriverTestCase, self).setUp() + self.config_file_path = None + self.create_fake_config_file_multi_slo_v3() + self.addCleanup(self._cleanup) + self.set_configuration() + + def set_configuration(self): + configuration = mock.Mock() + configuration.safe_get.return_value = 'MULTI_SLO_V3' + configuration.cinder_emc_config_file = self.config_file_path + configuration.config_group = 'MULTI_SLO_V3' + + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', + self.fake_ecom_connection) + instancename = FakeCIMInstanceName() + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', + instancename.fake_getinstancename) + self.stubs.Set(time, 'sleep', + self.fake_sleep) + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', + self.fake_is_v3) + + driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) + driver.db = FakeDB() + self.driver = driver + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) + + def create_fake_config_file_multi_slo_v3(self): + doc = minidom.Document() + emc = doc.createElement("EMC") + doc.appendChild(emc) + + eComServers = doc.createElement("EcomServers") + emc.appendChild(eComServers) + + eComServer = doc.createElement("EcomServer") + eComServers.appendChild(eComServer) + + ecomserverip = doc.createElement("EcomServerIp") + eComServer.appendChild(ecomserverip) + ecomserveriptext = doc.createTextNode("1.1.1.1") + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + eComServer.appendChild(ecomserverport) + ecomserverporttext = doc.createTextNode("10") + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + eComServer.appendChild(ecomusername) + ecomusernametext = doc.createTextNode("user") + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + eComServer.appendChild(ecompassword) + ecompasswordtext = doc.createTextNode("pass") + ecompassword.appendChild(ecompasswordtext) + + arrays = doc.createElement("Arrays") + eComServer.appendChild(arrays) + + array = doc.createElement("Array") + arrays.appendChild(array) + + serialNo = doc.createElement("SerialNumber") + array.appendChild(serialNo) + serialNoText = doc.createTextNode("1234567891011") + serialNo.appendChild(serialNoText) + + portgroups = doc.createElement("PortGroups") + array.appendChild(portgroups) + + portgroup = doc.createElement("PortGroup") + portgroups.appendChild(portgroup) + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + vpools = doc.createElement("Pools") + array.appendChild(vpools) + vpool = doc.createElement("Pool") + vpools.appendChild(vpool) + poolName = doc.createElement("PoolName") + vpool.appendChild(poolName) + poolNameText = doc.createTextNode("SRP_1") + poolName.appendChild(poolNameText) + poolslo = doc.createElement("SLO") + vpool.appendChild(poolslo) + poolsloText = doc.createTextNode("Bronze") + poolslo.appendChild(poolsloText) + poolworkload = doc.createElement("Workload") + vpool.appendChild(poolworkload) + poolworkloadText = doc.createTextNode("DSS") + poolworkload.appendChild(poolworkloadText) + + vpool2 = doc.createElement("Pool") + vpools.appendChild(vpool2) + pool2Name = doc.createElement("PoolName") + vpool2.appendChild(pool2Name) + pool2NameText = doc.createTextNode("SRP_1") + pool2Name.appendChild(pool2NameText) + pool2slo = doc.createElement("SLO") + vpool2.appendChild(pool2slo) + pool2sloText = doc.createTextNode("Silver") + pool2slo.appendChild(pool2sloText) + pool2workload = doc.createElement("Workload") + vpool.appendChild(pool2workload) + pool2workloadText = doc.createTextNode("OLTP") + pool2workload.appendChild(pool2workloadText) + + filename = 'cinder_emc_config_MULTI_SLO_V3.xml' + self.config_file_path = self.tempdir + '/' + filename + + f = open(self.config_file_path, 'w') + doc.writexml(f) + f.close() + + def fake_ecom_connection(self): + self.conn = FakeEcomConnection() + return self.conn + + def fake_sleep(self, seconds): + return + + def fake_is_v3(self, conn, serialNumber): + return True + + def default_extraspec(self): + return {'storagetype:pool': u'SRP_1', + 'volume_backend_name': 'MULTI_SLO_BE', + 'storagetype:workload': u'DSS', + 'storagetype:slo': u'Bronze', + 'storagetype:array': u'1234567891011', + 'isV3': True, + 'portgroupname': u'OS-portgroup-PG'} + + def test_initial_setup(self): + self.driver.common._register_config_file_from_config_group = ( + mock.Mock(return_value=self.config_file_path)) + extraSpecs = self.driver.common._initial_setup(self.vol_v3) + self.assertEqual('SRP_1', extraSpecs['storagetype:pool']) + self.assertEqual('DSS', extraSpecs['storagetype:workload']) + self.assertEqual('Bronze', extraSpecs['storagetype:slo']) + self.assertEqual('1234567891011', extraSpecs['storagetype:array']) + self.assertEqual('OS-portgroup-PG', extraSpecs['portgroupname']) + self.assertTrue(extraSpecs['isV3']) + + def test_validate_pool(self): + v3_valid_pool = self.data.test_volume_v3.copy() + # Pool aware scheduler enabled + v3_valid_pool['host'] = self.data.fake_host_v3 + pool = self.driver.common._validate_pool(v3_valid_pool) + self.assertEqual('Bronze+SRP_1+1234567891011', pool) + + # Cannot get the pool from the host + v3_valid_pool['host'] = 'HostX@Backend' + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.common._validate_pool, + v3_valid_pool) + # Legacy test. Provider Location does not have the version + v3_valid_pool['host'] = self.data.fake_host_v3 + v3_valid_pool['provider_location'] = self.data.provider_location + pool = self.driver.common._validate_pool(v3_valid_pool) + self.assertIsNone(pool) + + def test_array_info_multi_slo(self): + + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + self.assertTrue(len(arrayInfo) == 2) + for arrayInfoRec in arrayInfo: + self.assertEqual( + '1234567891011', arrayInfoRec['SerialNumber']) + self.assertTrue( + self.data.port_group in arrayInfoRec['PortGroup']) + self.assertTrue('SRP_1' in arrayInfoRec['PoolName']) + self.assertTrue( + 'Bronze' in arrayInfoRec['SLO'] or + 'Silver' in arrayInfoRec['SLO']) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_create_volume_multi_slo_success( + self, _mock_volume_type, mock_storage_system): + self.vol_v3['host'] = self.data.fake_host_v3 + self.vol_v3['provider_location'] = None + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_volume(self.vol_v3) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_delete_volume_multi_slo_success( + self, _mock_volume_type, mock_storage_system): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.delete_volume(self.vol_v3) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_create_volume_in_CG_multi_slo_success( + self, _mock_volume_type, mock_storage_system): + self.data.test_volume_CG_v3['provider_location'] = None + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_volume(self.data.test_volume_CG_v3) + + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_find_new_storage_group', + return_value='Any') + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'wrap_get_storage_group_from_volume', + return_value=None) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + '_get_fast_settings_from_storage_group', + return_value='Gold+DSS_REP') + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_retype_volume_multi_slo_success( + self, _mock_volume_type, mock_fast_settings, + mock_storage_group, mock_found_SG): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.assertTrue(self.driver.retype( + self.data.test_ctxt, self.data.test_volume_v3, self.data.new_type, + self.data.diff, self.data.test_host_v3)) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + # There is only one unique array in the conf file + def test_create_CG_multi_slo_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.common._initial_setup = mock.Mock( + return_value=self.default_extraspec()) + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_delete_CG_no_volumes_multi_slo_success( + self, _mock_volume_type, _mock_storage_system, + _mock_db_volumes, _mock_members): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_SLO_BE'}) + def test_delete_CG_with_volumes_multi_slo_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) + + +class EMCV2MultiPoolDriverMultipleEcomsTestCase(test.TestCase): + + def setUp(self): + + self.data = EMCVMAXCommonData() + self.vol_v2 = self.data.test_volume_v2 + self.vol_v2['provider_location'] = ( + six.text_type(self.data.provider_location_multi_pool)) + + self.tempdir = tempfile.mkdtemp() + super(EMCV2MultiPoolDriverMultipleEcomsTestCase, self).setUp() + self.config_file_path = None + self.create_fake_config_file_multi_ecom() + self.addCleanup(self._cleanup) + + configuration = mock.Mock() + configuration.cinder_emc_config_file = self.config_file_path + configuration.safe_get.return_value = 'MULTI_ECOM' + configuration.config_group = 'MULTI_ECOM' + + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', + self.fake_ecom_connection) + instancename = FakeCIMInstanceName() + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', + instancename.fake_getinstancename) + self.stubs.Set(time, 'sleep', + self.fake_sleep) + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', + self.fake_is_v3) + + driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) + driver.db = FakeDB() + driver.common.conn = FakeEcomConnection() + driver.zonemanager_lookup_service = FakeLookupService() + self.driver = driver + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) + + def create_fake_config_file_multi_ecom(self): + doc = minidom.Document() + emc = doc.createElement("EMC") + doc.appendChild(emc) + + eComServers = doc.createElement("EcomServers") + emc.appendChild(eComServers) + + eComServer = doc.createElement("EcomServer") + eComServers.appendChild(eComServer) + + ecomserverip = doc.createElement("EcomServerIp") + eComServer.appendChild(ecomserverip) + ecomserveriptext = doc.createTextNode("1.1.1.1") + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + eComServer.appendChild(ecomserverport) + ecomserverporttext = doc.createTextNode("10") + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + eComServer.appendChild(ecomusername) + ecomusernametext = doc.createTextNode("user") + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + eComServer.appendChild(ecompassword) + ecompasswordtext = doc.createTextNode("pass") + ecompassword.appendChild(ecompasswordtext) + + arrays = doc.createElement("Arrays") + eComServer.appendChild(arrays) + + array = doc.createElement("Array") + arrays.appendChild(array) + + serialNo = doc.createElement("SerialNumber") + array.appendChild(serialNo) + serialNoText = doc.createTextNode("1110987654321") + serialNo.appendChild(serialNoText) + + portgroups = doc.createElement("PortGroups") + array.appendChild(portgroups) + + portgroup = doc.createElement("PortGroup") + portgroups.appendChild(portgroup) + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + pools = doc.createElement("Pools") + array.appendChild(pools) + + pool = doc.createElement("Pool") + pools.appendChild(pool) + poolName = doc.createElement("PoolName") + pool.appendChild(poolName) + poolNameText = doc.createTextNode("gold") + poolName.appendChild(poolNameText) + + pool2 = doc.createElement("Pool") + pools.appendChild(pool2) + pool2Name = doc.createElement("PoolName") + pool2.appendChild(pool2Name) + pool2NameText = doc.createTextNode("SATA_BRONZE1") + pool2Name.appendChild(pool2NameText) + pool2FastPolicy = doc.createElement("FastPolicy") + pool2.appendChild(pool2FastPolicy) + pool2FastPolicyText = doc.createTextNode("BRONZE1") + pool2FastPolicy.appendChild(pool2FastPolicyText) + + eComServer = doc.createElement("EcomServer") + eComServers.appendChild(eComServer) + + ecomserverip = doc.createElement("EcomServerIp") + eComServer.appendChild(ecomserverip) + ecomserveriptext = doc.createTextNode("1.1.1.1") + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + eComServer.appendChild(ecomserverport) + ecomserverporttext = doc.createTextNode("10") + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + eComServer.appendChild(ecomusername) + ecomusernametext = doc.createTextNode("user") + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + eComServer.appendChild(ecompassword) + ecompasswordtext = doc.createTextNode("pass") + ecompassword.appendChild(ecompasswordtext) + + arrays = doc.createElement("Arrays") + eComServer.appendChild(arrays) + + array = doc.createElement("Array") + arrays.appendChild(array) + + serialNo = doc.createElement("SerialNumber") + array.appendChild(serialNo) + serialNoText = doc.createTextNode("1234567891011") + serialNo.appendChild(serialNoText) + + portgroups = doc.createElement("PortGroups") + array.appendChild(portgroups) + + portgroup = doc.createElement("PortGroup") + portgroups.appendChild(portgroup) + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + pools = doc.createElement("Pools") + array.appendChild(pools) + + pool = doc.createElement("Pool") + pools.appendChild(pool) + poolName = doc.createElement("PoolName") + pool.appendChild(poolName) + poolNameText = doc.createTextNode("gold") + poolName.appendChild(poolNameText) + + pool2 = doc.createElement("Pool") + pools.appendChild(pool2) + pool2Name = doc.createElement("PoolName") + pool2.appendChild(pool2Name) + pool2NameText = doc.createTextNode("SATA_BRONZE1") + pool2Name.appendChild(pool2NameText) + pool2FastPolicy = doc.createElement("FastPolicy") + pool2.appendChild(pool2FastPolicy) + pool2FastPolicyText = doc.createTextNode("BRONZE1") + pool2FastPolicy.appendChild(pool2FastPolicyText) + + filename = 'cinder_emc_config_V2_MULTI_ECOM.xml' + self.config_file_path = self.tempdir + '/' + filename + + f = open(self.config_file_path, 'w') + doc.writexml(f) + f.close() + + def fake_ecom_connection(self): + self.conn = FakeEcomConnection() + return self.conn + + def fake_sleep(self, seconds): + return + + def fake_is_v3(self, conn, serialNumber): + return False + + def test_array_info_multi_ecom_no_fast(self): + pool = 'gold+1234567891011' + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + self.assertTrue(len(arrayInfo) == 4) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + + self.assertEqual('1234567891011', poolRec['SerialNumber']) + self.assertEqual(self.data.port_group, poolRec['PortGroup']) + self.assertEqual(self.data.poolname, poolRec['PoolName']) + self.assertEqual('user', poolRec['EcomUserName']) + self.assertEqual('pass', poolRec['EcomPassword']) + self.assertEqual(None, poolRec['FastPolicy']) + self.assertFalse(poolRec['EcomUseSSL']) + + def test_array_info_multi_ecom_fast(self): + pool = 'SATA_BRONZE1+1234567891011' + + arrayInfo = self.driver.utils.parse_file_to_get_array_map( + self.config_file_path) + self.assertTrue(len(arrayInfo) == 4) + poolRec = self.driver.utils.extract_record(arrayInfo, pool) + + self.assertEqual('1234567891011', poolRec['SerialNumber']) + self.assertEqual(self.data.port_group, poolRec['PortGroup']) + self.assertEqual('SATA_BRONZE1', poolRec['PoolName']) + self.assertEqual('user', poolRec['EcomUserName']) + self.assertEqual('pass', poolRec['EcomPassword']) + self.assertEqual('BRONZE1', poolRec['FastPolicy']) + self.assertFalse(poolRec['EcomUseSSL']) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) + def test_create_volume_multi_ecom_success( + self, _mock_volume_type, mock_storage_system): + self.vol_v2['provider_location'] = None + self.driver.create_volume(self.vol_v2) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) + # If there are more than one unique arrays in conf file + def test_create_CG_multi_array_failure( + self, _mock_volume_type, _mock_storage_system): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_consistencygroup, + self.data.test_ctxt, + self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) + # There is more than one unique arrays in the conf file + def test_delete_CG_no_volumes_multi_array_failure( + self, _mock_volume_type, _mock_storage_system, + _mock_db_volumes, _mock_members): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.delete_consistencygroup, + self.data.test_ctxt, + self.data.test_CG) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) + def test_create_volume_in_CG_multi_ecom_success( + self, _mock_volume_type, mock_storage_system): + self.data.test_volume_CG['provider_location'] = None + self.driver.create_volume(self.data.test_volume_CG) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 9bc15bb3c..402d623fa 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -29,6 +29,7 @@ from cinder.volume.drivers.emc import emc_vmax_masking from cinder.volume.drivers.emc import emc_vmax_provision from cinder.volume.drivers.emc import emc_vmax_provision_v3 from cinder.volume.drivers.emc import emc_vmax_utils +from cinder.volume import utils as volume_utils LOG = logging.getLogger(__name__) @@ -44,6 +45,9 @@ except ImportError: CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml' CINDER_EMC_CONFIG_FILE_PREFIX = '/etc/cinder/cinder_emc_config_' CINDER_EMC_CONFIG_FILE_POSTFIX = '.xml' +BACKENDNAME = 'volume_backend_name' +PREFIXBACKENDNAME = 'capabilities:volume_backend_name' +PORTGROUPNAME = 'portgroupname' EMC_ROOT = 'root/emc' POOL = 'storagetype:pool' ARRAY = 'storagetype:array' @@ -54,7 +58,6 @@ STRIPECOUNT = 'storagetype:stripecount' MEMBERCOUNT = 'storagetype:membercount' STRIPED = 'striped' CONCATENATED = 'concatenated' -SMI_VERSION_8 = 800 # V3 SLO = 'storagetype:slo' WORKLOAD = 'storagetype:workload' @@ -88,14 +91,11 @@ class EMCVMAXCommon(object): 'vendor_name': 'EMC', 'volume_backend_name': None} - pool_info = {'pool_name': None, - 'fast_policy': None, - 'backend_name': None, - 'serial_number': None, - 'is_v3': False, - 'config_file': None} + pool_info = {'backend_name': None, + 'config_file': None, + 'arrays_info': {}} - def __init__(self, prtcl, configuration=None): + def __init__(self, prtcl, version, configuration=None): if not pywbemAvailable: LOG.info(_LI( @@ -114,6 +114,7 @@ class EMCVMAXCommon(object): self.fast = emc_vmax_fast.EMCVMAXFast(prtcl) self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl) self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl) + self.version = version self._gather_info() def _gather_info(self): @@ -133,33 +134,10 @@ class EMCVMAXCommon(object): {'emcConfigFileName': self.pool_info['config_file'], 'backendName': self.pool_info['backend_name']}) - if self.conn is None: - self._set_ecom_credentials(self.pool_info['config_file']) - - self.pool_info['serial_number'] = ( - self.utils.parse_array_name_from_file( + self.pool_info['arrays_info'] = ( + self.utils.parse_file_to_get_array_map( self.pool_info['config_file'])) - if self.pool_info['serial_number'] is None: - LOG.error(_LE( - "Array Serial Number %(arrayName)s must be in the file " - "%(emcConfigFileName)s."), - {'arrayName': self.pool_info['serial_number'], - 'emcConfigFileName': self.pool_info['config_file']}) - - self.pool_info['pool_name'] = ( - self.utils.parse_pool_name_from_file( - self.pool_info['config_file'])) - if self.pool_info['pool_name'] is None: - LOG.error(_LE( - "PoolName %(poolName)s must be in the file " - "%(emcConfigFileName)s."), - {'poolName': self.pool_info['pool_name'], - 'emcConfigFileName': self.pool_info['config_file']}) - - self.pool_info['is_v3'] = ( - self.utils.isArrayV3(self.conn, self.pool_info['serial_number'])) - def create_volume(self, volume): """Creates a EMC(VMAX) volume from a pre-existing storage pool. @@ -215,6 +193,8 @@ class EMCVMAXCommon(object): {'volumeName': volumeName, 'rc': rc, 'name': volumeDict}) + # Adding version information + volumeDict['version'] = self.version return volumeDict @@ -229,7 +209,8 @@ class EMCVMAXCommon(object): :raises: VolumeBackendAPIException """ LOG.debug("Entering create_volume_from_snapshot.") - extraSpecs = self._initial_setup(volume) + snapshot['host'] = volume['host'] + extraSpecs = self._initial_setup(snapshot) self.conn = self._get_ecom_connection() snapshotInstance = self._find_lun(snapshot) storageSystem = snapshotInstance['SystemName'] @@ -249,7 +230,8 @@ class EMCVMAXCommon(object): self.provision.delete_clone_relationship( self.conn, repservice, syncName, extraSpecs) - return self._create_cloned_volume(volume, snapshot, False) + snapshot['host'] = volume['host'] + return self._create_cloned_volume(volume, snapshot, extraSpecs, False) def create_cloned_volume(self, cloneVolume, sourceVolume): """Creates a clone of the specified volume. @@ -258,7 +240,9 @@ class EMCVMAXCommon(object): :param sourceVolume: volume object :returns: cloneVolumeDict -- the cloned volume dictionary """ - return self._create_cloned_volume(cloneVolume, sourceVolume, False) + extraSpecs = self._initial_setup(sourceVolume) + return self._create_cloned_volume(cloneVolume, sourceVolume, + extraSpecs, False) def delete_volume(self, volume): """Deletes a EMC(VMAX) volume. @@ -283,7 +267,8 @@ class EMCVMAXCommon(object): :param volume: volume Object to create snapshot from :returns: dict -- the cloned volume dictionary """ - return self._create_cloned_volume(snapshot, volume, True) + extraSpecs = self._initial_setup(volume) + return self._create_cloned_volume(snapshot, volume, extraSpecs, True) def delete_snapshot(self, snapshot, volume): """Deletes a snapshot. @@ -293,6 +278,7 @@ class EMCVMAXCommon(object): """ LOG.info(_LI("Delete Snapshot: %(snapshotName)s."), {'snapshotName': snapshot['name']}) + snapshot['host'] = volume['host'] self._delete_snapshot(snapshot) def _remove_members(self, controllerConfigService, @@ -564,64 +550,76 @@ class EMCVMAXCommon(object): def update_volume_stats(self): """Retrieve stats info.""" - - if self.pool_info['is_v3']: - location_info, total_capacity_gb, free_capacity_gb = ( - self._update_srp_stats(self.pool_info['config_file'], - self.pool_info['serial_number'], - self.pool_info['pool_name'])) - else: - # This is V2. - location_info, total_capacity_gb, free_capacity_gb = ( - self._update_pool_stats(self.pool_info['config_file'], - self.pool_info['backend_name'], - self.pool_info['serial_number'], - self.pool_info['pool_name'])) - - data = {'total_capacity_gb': total_capacity_gb, - 'free_capacity_gb': free_capacity_gb, - 'reserved_percentage': 0, - 'QoS_support': False, + pools = [] + backendName = self.pool_info['backend_name'] + for arrayInfo in self.pool_info['arrays_info']: + self._set_ecom_credentials(arrayInfo) + # Check what type of array it is + isV3 = self.utils.isArrayV3(self.conn, arrayInfo['SerialNumber']) + if isV3: + location_info, total_capacity_gb, free_capacity_gb = ( + self._update_srp_stats(arrayInfo)) + poolName = ("%(slo)s+%(poolName)s+%(array)s" + % {'slo': arrayInfo['SLO'], + 'poolName': arrayInfo['PoolName'], + 'array': arrayInfo['SerialNumber']}) + else: + # This is V2 + location_info, total_capacity_gb, free_capacity_gb = ( + self._update_pool_stats(backendName, arrayInfo)) + poolName = ("%(poolName)s+%(array)s" + % {'poolName': arrayInfo['PoolName'], + 'array': arrayInfo['SerialNumber']}) + + pool = {'pool_name': poolName, + 'total_capacity_gb': total_capacity_gb, + 'free_capacity_gb': free_capacity_gb, + 'reserved_percentage': 0, + 'QoS_support': False, + 'location_info': location_info, + 'consistencygroup_support': True} + pools.append(pool) + + data = {'vendor_name': "EMC", + 'driver_version': self.version, + 'storage_protocol': 'unknown', 'volume_backend_name': self.pool_info['backend_name'] or self.__class__.__name__, - 'vendor_name': "EMC", - 'driver_version': self.VERSION, - 'storage_protocol': 'unknown', - 'location_info': location_info, - 'consistencygroup_support': True} + # Use zero capacities here so we always use a pool. + 'total_capacity_gb': 0, + 'free_capacity_gb': 0, + 'reserved_percentage': 0, + 'pools': pools} return data - def _update_srp_stats(self, emcConfigFileName, arrayName, poolName): + def _update_srp_stats(self, arrayInfo): """Update SRP stats. - :param emcConfigFileName: the EMC configuration file - :param arrayName: the array - :param poolName: the pool + :param arrayInfo: array information :returns: location_info :returns: totalManagedSpaceGbs :returns: remainingManagedSpaceGbs """ totalManagedSpaceGbs, remainingManagedSpaceGbs = ( - self.utils.get_srp_pool_stats(self.conn, arrayName, poolName)) + self.provisionv3.get_srp_pool_stats(self.conn, + arrayInfo)) LOG.info(_LI( "Capacity stats for SRP pool %(poolName)s on array " "%(arrayName)s total_capacity_gb=%(total_capacity_gb)lu, " - "free_capacity_gb=%(free_capacity_gb)lu."), - {'poolName': poolName, - 'arrayName': arrayName, + "free_capacity_gb=%(free_capacity_gb)lu"), + {'poolName': arrayInfo['PoolName'], + 'arrayName': arrayInfo['SerialNumber'], 'total_capacity_gb': totalManagedSpaceGbs, 'free_capacity_gb': remainingManagedSpaceGbs}) - slo = self.utils.parse_slo_from_file(emcConfigFileName) - workload = self.utils.parse_workload_from_file(emcConfigFileName) location_info = ("%(arrayName)s#%(poolName)s#%(slo)s#%(workload)s" - % {'arrayName': arrayName, - 'poolName': poolName, - 'slo': slo, - 'workload': workload}) + % {'arrayName': arrayInfo['SerialNumber'], + 'poolName': arrayInfo['PoolName'], + 'slo': arrayInfo['SLO'], + 'workload': arrayInfo['Workload']}) return location_info, totalManagedSpaceGbs, remainingManagedSpaceGbs @@ -1203,7 +1201,6 @@ class EMCVMAXCommon(object): # If there are no extra specs then the default case is assumed. if extraSpecs: configGroup = self.configuration.config_group - configurationFile = self._register_config_file_from_config_group( configGroup) @@ -1317,23 +1314,13 @@ 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. - try: - foundVolumeinstance = self.conn.GetInstance(instancename) - except Exception: - foundVolumeinstance = None + foundVolumeinstance = self.utils.get_existing_instance( + self.conn, instancename) if foundVolumeinstance is None: LOG.debug("Volume %(volumename)s not found on the array.", @@ -1566,7 +1553,6 @@ class EMCVMAXCommon(object): :returns: string -- configurationFile - name of the configuration file """ if configGroupName is None: - self._set_ecom_credentials(CINDER_EMC_CONFIG_FILE) return CINDER_EMC_CONFIG_FILE if hasattr(self.configuration, 'cinder_emc_config_file'): configurationFile = self.configuration.cinder_emc_config_file @@ -1586,15 +1572,6 @@ class EMCVMAXCommon(object): 'configGroupName': configGroupName, 'postfix': CINDER_EMC_CONFIG_FILE_POSTFIX})) - self._set_ecom_credentials(configurationFile) - return configurationFile - - def _set_ecom_credentials(self, configurationFile): - """Given the configuration file set the ecom credentials. - - :param configurationFile: name of the file (String) - :raises: VolumeBackendAPIException - """ if os.path.isfile(configurationFile): LOG.debug("Configuration file : %(configurationFile)s exists.", {'configurationFile': configurationFile}) @@ -1605,10 +1582,21 @@ class EMCVMAXCommon(object): LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) - ip, port = self.utils.get_ecom_server(configurationFile) - self.user, self.passwd = self.utils.get_ecom_cred(configurationFile) - self.ecomUseSSL, self.ecomCACert, self.ecomNoVerification = ( - self.utils.get_ecom_cred_SSL(configurationFile)) + return configurationFile + + def _set_ecom_credentials(self, arrayInfo): + """Given the array record set the ecom credentials. + + :param arrayInfo: record + :raises: VolumeBackendAPIException + """ + ip = arrayInfo['EcomServerIp'] + port = arrayInfo['EcomServerPort'] + self.user = arrayInfo['EcomUserName'] + self.passwd = arrayInfo['EcomPassword'] + self.ecomUseSSL = arrayInfo['EcomUseSSL'] + self.ecomCACert = arrayInfo['EcomCACert'] + self.ecomNoVerification = arrayInfo['EcomNoVerification'] ip_port = ("%(ip)s:%(port)s" % {'ip': ip, 'port': port}) @@ -1641,33 +1629,34 @@ class EMCVMAXCommon(object): self._set_config_file_and_get_extra_specs( volume, volumeTypeId)) - arrayName = self.utils.parse_array_name_from_file( + pool = self._validate_pool(volume) + LOG.debug("Pool returned is %(pool)s.", + {'pool': pool}) + arrayInfo = self.utils.parse_file_to_get_array_map( configurationFile) - if arrayName is None: + poolRecord = self.utils.extract_record(arrayInfo, pool) + + if not poolRecord: exceptionMessage = (_( - "The array cannot be null. The pool must be configured " - "either as a cinder extra spec for multi-backend or in " - "the EMC configuration file for the default case.")) - LOG.error(exceptionMessage) + "Unable to get corresponding record for pool.")) raise exception.VolumeBackendAPIException( data=exceptionMessage) - isV3 = self.utils.isArrayV3(self.conn, arrayName) + self._set_ecom_credentials(poolRecord) + isV3 = self.utils.isArrayV3( + self.conn, poolRecord['SerialNumber']) if isV3: - extraSpecs = self._set_v3_extra_specs( - configurationFile, arrayName, extraSpecs) + extraSpecs = self._set_v3_extra_specs(extraSpecs, poolRecord) else: - # V2 extra specs. - extraSpecs = self._set_v2_extra_specs( - configurationFile, arrayName, extraSpecs) + # V2 extra specs + extraSpecs = self._set_v2_extra_specs(extraSpecs, poolRecord) except Exception: + import sys exceptionMessage = (_( - "Unable to get configuration information necessary to create " - "a volume. Please check that there is a configuration file " - "for each config group, if multi-backend is enabled. " - "The file should be in the following format " - "/etc/cinder/cinder_emc_config_.xml.")) + "Unable to get configuration information necessary to " + "create a volume: %(errorMessage)s.") + % {'errorMessage': sys.exc_info()[1]}) raise exception.VolumeBackendAPIException(data=exceptionMessage) return extraSpecs @@ -1732,9 +1721,7 @@ class EMCVMAXCommon(object): % {'shortHostName': shortHostName, 'poolName': poolName, 'protocol': protocol})) - maskingViewDict['fastPolicy'] = ( - self.utils.parse_fast_policy_name_from_file( - self.configuration.cinder_emc_config_file)) + maskingViewDict['fastPolicy'] = extraSpecs[FASTPOLICY] maskingViewDict['sgGroupName'] = ("%(prefix)s-SG" % {'prefix': prefix}) @@ -1749,9 +1736,7 @@ class EMCVMAXCommon(object): self.utils.find_controller_configuration_service( self.conn, storageSystemName)) # The portGroup is gotten from emc xml config file. - maskingViewDict['pgGroupName'] = ( - self.utils.parse_file_to_get_port_group_name( - self.configuration.cinder_emc_config_file)) + maskingViewDict['pgGroupName'] = extraSpecs[PORTGROUPNAME] maskingViewDict['igGroupName'] = ( ("OS-%(shortHostName)s-%(protocol)s-IG" @@ -1843,8 +1828,8 @@ class EMCVMAXCommon(object): if 'True' in isVolumeBound: appendVolumeInstance = ( self._unbind_and_get_volume_from_storage_pool( - conn, storageConfigService, appendVolumeInstance.path, - 'appendVolume', extraSpecs)) + conn, storageConfigService, assocPoolInstanceName, + appendVolumeInstance.path, 'appendVolume', extraSpecs)) return appendVolumeInstance @@ -1870,33 +1855,27 @@ class EMCVMAXCommon(object): return volumeInstance def _unbind_and_get_volume_from_storage_pool( - self, conn, storageConfigService, volumeInstanceName, - volumeName, extraSpecs): + self, conn, storageConfigService, poolInstanceName, + 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, volumeInstanceName, + conn, storageConfigService, poolInstanceName, + volumeInstanceName, volumeName, extraSpecs)) - # 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) - + volumeDict = self.provision.get_volume_dict_from_job(conn, job['Job']) + volumeInstance = self.utils.find_volume_instance( + self.conn, volumeDict, volumeName) return volumeInstance def _modify_and_get_composite_volume_instance( @@ -1964,16 +1943,16 @@ class EMCVMAXCommon(object): return defaultStorageGroupInstanceName def _create_cloned_volume( - self, cloneVolume, sourceVolume, isSnapshot=False): + self, cloneVolume, sourceVolume, extraSpecs, isSnapshot=False): """Create a clone volume from the source volume. :param cloneVolume: clone volume :param sourceVolume: source of the clone volume + :param extraSpecs: extra specs :param isSnapshot: boolean -- Defaults to False :returns: dict -- cloneDict the cloned volume dictionary + :raises: VolumeBackendAPIException """ - extraSpecs = self._initial_setup(cloneVolume) - sourceName = sourceVolume['name'] cloneName = cloneVolume['name'] @@ -2035,6 +2014,8 @@ class EMCVMAXCommon(object): {'cloneName': cloneName, 'sourceName': sourceName, 'rc': rc}) + # Adding version information + cloneDict['version'] = self.version return cloneDict @@ -2823,28 +2804,6 @@ class EMCVMAXCommon(object): storageConfigService = self.utils.find_storage_configuration_service( self.conn, storageSystemName) - # Check the SLO range. - maximumVolumeSize, minimumVolumeSize = ( - self.provisionv3.get_volume_range( - self.conn, storageConfigService, poolInstanceName, - extraSpecs[SLO], extraSpecs[WORKLOAD], - extraSpecs)) - if not self.utils.is_in_range( - volumeSize, maximumVolumeSize, minimumVolumeSize): - LOG.warning(_LW( - "Volume: %(volume)s with size: %(volumeSize)s bits " - "is not in the Performance Capacity range: " - "%(minimumVolumeSize)s-%(maximumVolumeSize)s bits. " - "for SLO:%(slo)s and workload:%(workload)s. " - "Unpredictable results may occur."), - {'volume': volumeName, - 'volumeSize': volumeSize, - 'minimumVolumeSize': minimumVolumeSize, - 'maximumVolumeSize': maximumVolumeSize, - 'slo': extraSpecs[SLO], - 'workload': extraSpecs[WORKLOAD] - }) - # A volume created without specifying a storage group during # creation time is allocated from the default SRP pool and # assigned the optimized SLO. @@ -2979,7 +2938,6 @@ class EMCVMAXCommon(object): storageGroupName)) storageSystemName = volumeInstance['SystemName'] - if not isValid: LOG.error(_LE( "Volume %(name)s is not suitable for storage " @@ -3022,6 +2980,7 @@ class EMCVMAXCommon(object): controllerConfigService = ( self.utils.find_controller_configuration_service( self.conn, storageSystemName)) + defaultSgName = self.utils.get_v3_storage_group_name( extraSpecs[POOL], extraSpecs[SLO], extraSpecs[WORKLOAD]) @@ -3122,83 +3081,72 @@ class EMCVMAXCommon(object): return False def _update_pool_stats( - self, emcConfigFileName, backendName, arrayName, poolName): + self, backendName, arrayInfo): """Update pool statistics (V2). - :param emcConfigFileName: the EMC configuration file :param backendName: the backend name - :param arrayName: the array name - :param poolName: the pool name + :param arrayInfo: the arrayInfo :returns: location_info, total_capacity_gb, free_capacity_gb """ - # This value can be None. - fastPolicyName = self.utils.parse_fast_policy_name_from_file( - emcConfigFileName) - if fastPolicyName is not None: + + if arrayInfo['FastPolicy']: LOG.debug( "Fast policy %(fastPolicyName)s is enabled on %(arrayName)s.", - {'fastPolicyName': fastPolicyName, - 'arrayName': arrayName}) + {'fastPolicyName': arrayInfo['FastPolicy'], + 'arrayName': arrayInfo['SerialNumber']}) else: LOG.debug( "No Fast policy for Array:%(arrayName)s " "backend:%(backendName)s.", - {'arrayName': arrayName, + {'arrayName': arrayInfo['SerialNumber'], 'backendName': backendName}) storageSystemInstanceName = self.utils.find_storageSystem( - self.conn, arrayName) + self.conn, arrayInfo['SerialNumber']) isTieringPolicySupported = ( self.fast.is_tiering_policy_enabled_on_storage_system( self.conn, storageSystemInstanceName)) - if (fastPolicyName is not None and - isTieringPolicySupported is True): # FAST enabled. + if (arrayInfo['FastPolicy'] is not None and + isTieringPolicySupported is True): # FAST enabled total_capacity_gb, free_capacity_gb = ( self.fast.get_capacities_associated_to_policy( - self.conn, arrayName, fastPolicyName)) + self.conn, arrayInfo['SerialNumber'], + arrayInfo['FastPolicy'])) LOG.info(_LI( - "FAST: capacity stats for policy %(fastPolicyName)s on " - "array: %(arrayName)s total_capacity_gb=%(total_capacity_gb)lu" - ", free_capacity_gb=%(free_capacity_gb)lu."), - {'fastPolicyName': fastPolicyName, - 'arrayName': arrayName, + "FAST: capacity stats for policy %(fastPolicyName)s on array " + "%(arrayName)s. total_capacity_gb=%(total_capacity_gb)lu, " + "free_capacity_gb=%(free_capacity_gb)lu."), + {'fastPolicyName': arrayInfo['FastPolicy'], + 'arrayName': arrayInfo['SerialNumber'], 'total_capacity_gb': total_capacity_gb, 'free_capacity_gb': free_capacity_gb}) else: # NON-FAST total_capacity_gb, free_capacity_gb = ( - self.utils.get_pool_capacities(self.conn, poolName, arrayName)) + self.utils.get_pool_capacities(self.conn, + arrayInfo['PoolName'], + arrayInfo['SerialNumber'])) LOG.info(_LI( - "NON-FAST: capacity stats for pool %(poolName)s on array: " + "NON-FAST: capacity stats for pool %(poolName)s on array " "%(arrayName)s total_capacity_gb=%(total_capacity_gb)lu, " "free_capacity_gb=%(free_capacity_gb)lu."), - {'poolName': poolName, - 'arrayName': arrayName, + {'poolName': arrayInfo['PoolName'], + 'arrayName': arrayInfo['SerialNumber'], 'total_capacity_gb': total_capacity_gb, 'free_capacity_gb': free_capacity_gb}) - if poolName is None: - LOG.debug("Unable to get the poolName for location_info.") - if arrayName is None: - LOG.debug("Unable to get the arrayName for location_info.") - if fastPolicyName is None: - LOG.debug("FAST is not enabled for this configuration: " - "%(emcConfigFileName)s.", - {'emcConfigFileName': emcConfigFileName}) - location_info = ("%(arrayName)s#%(poolName)s#%(policyName)s" - % {'arrayName': arrayName, - 'poolName': poolName, - 'policyName': fastPolicyName}) + % {'arrayName': arrayInfo['SerialNumber'], + 'poolName': arrayInfo['PoolName'], + 'policyName': arrayInfo['FastPolicy']}) return location_info, total_capacity_gb, free_capacity_gb - def _set_v2_extra_specs(self, configurationFile, arrayName, extraSpecs): + def _set_v2_extra_specs(self, extraSpecs, poolRecord): """Set the VMAX V2 extra specs. - :param configurationFile: the EMC configuration file - :param arrayName: the array serial number :param extraSpecs: extra specifications + :param poolRecord: pool record :returns: dict -- the extraSpecs :raises: VolumeBackendAPIException """ @@ -3217,29 +3165,14 @@ class EMCVMAXCommon(object): extraSpecs[COMPOSITETYPE] = CONCATENATED LOG.debug("StripedMetaCount is not in the extra specs.") - poolName = self.utils.parse_pool_name_from_file(configurationFile) - if poolName is None: - exceptionMessage = (_( - "The pool cannot be null. The pool must be configured " - "either in the extra specs or in the EMC configuration " - "file corresponding to the Volume Type.")) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - # Get the FAST policy from the file. This value can be None if the # user doesn't want to associate with any FAST policy. - fastPolicyName = self.utils.parse_fast_policy_name_from_file( - configurationFile) - if fastPolicyName is not None: + if poolRecord['FastPolicy']: LOG.debug("The fast policy name is: %(fastPolicyName)s.", - {'fastPolicyName': fastPolicyName}) - - extraSpecs[POOL] = poolName - extraSpecs[ARRAY] = arrayName - extraSpecs[FASTPOLICY] = fastPolicyName + {'fastPolicyName': poolRecord['FastPolicy']}) + extraSpecs[FASTPOLICY] = poolRecord['FastPolicy'] extraSpecs[ISV3] = False - extraSpecs = self._get_job_extra_specs(configurationFile, extraSpecs) + extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord) LOG.debug("Pool is: %(pool)s " "Array is: %(array)s " @@ -3253,27 +3186,21 @@ class EMCVMAXCommon(object): 'memberCount': extraSpecs[MEMBERCOUNT]}) return extraSpecs - def _set_v3_extra_specs(self, configurationFile, arrayName, extraSpecs): + def _set_v3_extra_specs(self, extraSpecs, poolRecord): """Set the VMAX V3 extra specs. If SLO or workload are not specified then the default values are NONE and the Optimized SLO will be assigned to the volume. - :param configurationFile: the EMC configuration file - :param arrayName: the array serial number - :returns: dict -- the extraSpecs + :param extraSpecs: extra specifications + :param poolRecord: pool record + :returns: dict -- the extra specifications dictionary """ - extraSpecs[SLO] = self.utils.parse_slo_from_file( - configurationFile) - extraSpecs[WORKLOAD] = self.utils.parse_workload_from_file( - configurationFile) - extraSpecs[POOL] = self.utils.parse_pool_name_from_file( - configurationFile) - extraSpecs[ARRAY] = arrayName + extraSpecs[SLO] = poolRecord['SLO'] + extraSpecs[WORKLOAD] = poolRecord['Workload'] extraSpecs[ISV3] = True - extraSpecs = self._get_job_extra_specs(configurationFile, extraSpecs) - + extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord) LOG.debug("Pool is: %(pool)s " "Array is: %(array)s " "SLO is: %(slo)s " @@ -3284,27 +3211,30 @@ class EMCVMAXCommon(object): 'workload': extraSpecs[WORKLOAD]}) return extraSpecs - def _get_job_extra_specs(self, configurationFile, extraSpecs): - """Get user defined extra specs around job intervals and retries. + def _set_common_extraSpecs(self, extraSpecs, poolRecord): + """Set common extra specs. + + The extraSpecs are common to v2 and v3 - :param configurationFile: the EMC configuration file - :param extraSpecs: extraSpecs (in) - :returns: extraSpecs (out) + :param extraSpecs: extra specifications + :param poolRecord: pool record + :returns: dict -- the extra specifications dictionary """ - intervalInSecs = self.utils.parse_interval_from_file( - configurationFile) - if intervalInSecs is not None: + extraSpecs[POOL] = poolRecord['PoolName'] + extraSpecs[ARRAY] = poolRecord['SerialNumber'] + extraSpecs[PORTGROUPNAME] = poolRecord['PortGroup'] + if 'Interval' in poolRecord and poolRecord['Interval']: + extraSpecs[INTERVAL] = poolRecord['Interval'] LOG.debug("The user defined interval is : %(intervalInSecs)s.", - {'intervalInSecs': intervalInSecs}) - extraSpecs[INTERVAL] = intervalInSecs - - retries = self.utils.parse_retries_from_file( - configurationFile) - if retries is not None: + {'intervalInSecs': poolRecord['Interval']}) + else: + LOG.debug("Interval not overridden, default of 10 assumed.") + if 'Retries' in poolRecord and poolRecord['Retries']: + extraSpecs[RETRIES] = poolRecord['Retries'] LOG.debug("The user defined retries is : %(retries)s.", - {'retries': retries}) - extraSpecs[RETRIES] = retries - + {'retries': poolRecord['Retries']}) + else: + LOG.debug("Retries not overridden, default of 60 assumed.") return extraSpecs def _delete_from_pool(self, storageConfigService, volumeInstance, @@ -3348,8 +3278,12 @@ class EMCVMAXCommon(object): controllerConfigurationService, volumeInstance.path, volumeName, extraSpecs) - LOG.debug("Deleting Volume: %(name)s with deviceId: %(deviceId)s.", - {'name': volumeName, + 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, 'deviceId': deviceId}) try: rc = self.provision.delete_volume_from_pool( @@ -3382,7 +3316,6 @@ class EMCVMAXCommon(object): {'volumeName': volumeName}) LOG.exception(errorMessage) raise exception.VolumeBackendAPIException(data=errorMessage) - return rc def _delete_from_pool_v3(self, storageConfigService, volumeInstance, @@ -3476,9 +3409,9 @@ class EMCVMAXCommon(object): else: # Composite volume with meta device members. # Check if the meta members capacity. metaMemberInstanceNames = ( - self.utils.get_composite_elements( - self.conn, sourceInstance)) - volumeCapacities = self.utils.get_meta_members_capacity_in_byte( + self.utils.get_meta_members_of_composite_volume( + self.conn, metaHeadInstanceName)) + volumeCapacities = self.utils.get_meta_members_capacity_in_bit( self.conn, metaMemberInstanceNames) LOG.debug("Volume capacities: %(metasizes)s.", {'metasizes': volumeCapacities}) @@ -3741,9 +3674,6 @@ class EMCVMAXCommon(object): # Default operation 8: Detach for clone. operation = self.utils.get_num(8, '16') - # Create target volume - extraSpecs = self._initial_setup(cloneVolume) - numOfBlocks = sourceInstance['NumberOfBlocks'] blockSize = sourceInstance['BlockSize'] volumeSizeInbits = numOfBlocks * blockSize @@ -3904,6 +3834,67 @@ class EMCVMAXCommon(object): extraSpecs) return rc + def _validate_pool(self, volume): + """Get the pool from volume['host']. + + There may be backward compatibiliy concerns, so putting in a + check to see if a version has been added to provider_location. + If it has, we know we are at the current version, if not, we + assume it was created pre 'Pool Aware Scheduler' feature. + + :param volume: the volume Object + :returns: string -- pool + :raises: VolumeBackendAPIException + """ + pool = None + # Volume is None in CG ops. + if volume is None: + return pool + + # This check is for all operations except a create. + # On a create provider_location is None + try: + if volume['provider_location']: + version = self._get_version_from_provider_location( + volume['provider_location']) + if not version: + return pool + except KeyError: + return pool + try: + pool = volume_utils.extract_host(volume['host'], 'pool') + if pool: + LOG.debug("Pool from volume['host'] is %(pool)s.", + {'pool': pool}) + else: + exceptionMessage = (_( + "Pool from volume['host'] %(host)s not found.") + % {'host': volume['host']}) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + except Exception as ex: + exceptionMessage = (_( + "Pool from volume['host'] failed with: %(ex)s.") + % {'ex': ex}) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + return pool + + def _get_version_from_provider_location(self, loc): + """Get the version from the provider location. + + :param loc: the provider_location dict + :returns: version or None + """ + version = None + try: + if isinstance(loc, six.string_types): + name = eval(loc) + version = name['version'] + except KeyError: + pass + return version + def manage_existing(self, volume, external_ref): """Manages an existing VMAX Volume (import to Cinder). diff --git a/cinder/volume/drivers/emc/emc_vmax_fast.py b/cinder/volume/drivers/emc/emc_vmax_fast.py index 1c3a24ea3..5fc9b8ff0 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fast.py +++ b/cinder/volume/drivers/emc/emc_vmax_fast.py @@ -431,8 +431,7 @@ class EMCVMAXFast(object): storageMaskingGroupInstances = conn.Associators( controllerConfigService, ResultClass='CIM_DeviceMaskingGroup') - for storageMaskingGroupInstance in \ - storageMaskingGroupInstances: + for storageMaskingGroupInstance in storageMaskingGroupInstances: if ('_default_' in storageMaskingGroupInstance['ElementName'] and policyName in storageMaskingGroupInstance['ElementName']): diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 93da018b7..7748abb17 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -40,15 +40,17 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 2.2.0 - Add manage/unmanage 2.2.1 - Support for SE 8.0.3 2.2.2 - Update Consistency Group + 2.2.3 - Pool aware scheduler(multi-pool) support """ - VERSION = "2.2.2" + VERSION = "2.2.3" def __init__(self, *args, **kwargs): super(EMCVMAXFCDriver, self).__init__(*args, **kwargs) self.common = emc_vmax_common.EMCVMAXCommon( 'FC', + self.VERSION, configuration=self.configuration) self.zonemanager_lookup_service = fczm_utils.create_lookup_service() diff --git a/cinder/volume/drivers/emc/emc_vmax_https.py b/cinder/volume/drivers/emc/emc_vmax_https.py index ea08ebfc0..46b0a583f 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 httplib requires + Supplies an additional 'makefile' method which http_client 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(httplib.HTTPSConnection): +class HTTPSConnection(http_client.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(httplib.HTTPSConnection): else: excp_lst = () try: - httplib.HTTPSConnection.__init__(self, host, port, - key_file=key_file, - cert_file=cert_file) + http_client.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 httplib. x509 may be a + response in XML. Uses Python's build-in http_client. 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, unicode): + if isinstance(data, six.text_type): 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, unicode): + if isinstance(hdr, six.text_type): hdr = hdr.encode('utf-8') s = map(lambda x: string.strip(x), string.split(hdr, ":", 1)) - h.putheader(urllib.quote(s[0]), urllib.quote(s[1])) + h.putheader(urllib.parse.quote(s[0]), urllib.parse.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 httplib.BadStatusLine as arg: + except http_client.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 5c3d253b3..1477392b8 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -48,15 +48,17 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 2.2.0 - Add manage/unmanage 2.2.1 - Support for SE 8.0.3 2.2.2 - Update Consistency Group + 2.2.3 - Pool aware scheduler(multi-pool) support """ - VERSION = "2.2.2" + VERSION = "2.2.3" def __init__(self, *args, **kwargs): super(EMCVMAXISCSIDriver, self).__init__(*args, **kwargs) self.common = ( emc_vmax_common.EMCVMAXCommon('iSCSI', + self.VERSION, configuration=self.configuration)) def check_for_setup_error(self): diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py index 9725cef7e..18efe241d 100644 --- a/cinder/volume/drivers/emc/emc_vmax_masking.py +++ b/cinder/volume/drivers/emc/emc_vmax_masking.py @@ -623,6 +623,7 @@ class EMCVMAXMasking(object): :param conn: the connection to ecom :param storageGroupInstanceName: the storage group instance name :param volumeInstance: the volume instance + :param sgName: the storage group name :returns: boolean """ foundStorageGroupInstanceName = ( @@ -901,8 +902,8 @@ class EMCVMAXMasking(object): conn.AssociatorNames(controllerConfigService, ResultClass='CIM_InitiatorMaskingGroup')) - for initiatorMaskingGroupInstanceName in \ - initiatorMaskingGroupInstanceNames: + for initiatorMaskingGroupInstanceName in ( + initiatorMaskingGroupInstanceNames): # Check that it hasn't been deleted. If it has, break out # of the for loop. instance = self.utils.get_existing_instance( @@ -919,8 +920,8 @@ class EMCVMAXMasking(object): # we found the existing CIM_InitiatorMaskingGroup. hardwareid = storageHardwareIdInstance['StorageID'] for initiator in initiatorNames: - if six.text_type(hardwareid).lower() == \ - six.text_type(initiator).lower(): + if six.text_type(hardwareid).lower() == ( + six.text_type(initiator).lower()): foundInitiatorMaskingGroupInstanceName = ( initiatorMaskingGroupInstanceName) break @@ -2037,10 +2038,10 @@ class EMCVMAXMasking(object): maskingViewDict['sgGroupName'] = defaultStorageGroupName maskingViewDict['volumeInstance'] = volumeInstance maskingViewDict['volumeName'] = volumeName - maskingViewDict['controllerConfigService'] = \ - controllerConfigService - maskingViewDict['storageSystemName'] = \ - storageSystemInstanceName + maskingViewDict['controllerConfigService'] = ( + controllerConfigService) + maskingViewDict['storageSystemName'] = ( + storageSystemInstanceName) sgInstanceName = self.utils.find_storage_masking_group( conn, controllerConfigService, defaultStorageGroupName) if sgInstanceName is not None: diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 4948a8cb8..6a485e0b8 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( - 'ReturnElementsToStoragePool', storageConfigservice, + 'EMCReturnToStoragePool', storageConfigservice, TheElements=theElements) if rc != 0: @@ -338,13 +338,14 @@ class EMCVMAXProvision(object): time.time())}) def unbind_volume_from_storage_pool( - self, conn, storageConfigService, volumeInstanceName, - volumeName, extraSpecs): + self, conn, storageConfigService, poolInstanceName, + 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 @@ -357,6 +358,7 @@ class EMCVMAXProvision(object): rc, job = conn.InvokeMethod( 'EMCUnBindElement', storageConfigService, + InPool=poolInstanceName, TheElement=volumeInstanceName) if rc != 0: @@ -1079,16 +1081,14 @@ class EMCVMAXProvision(object): 'relationName': relationName, 'srcGroup': srcGroupInstanceName, 'tgtGroup': tgtGroupInstanceName}) - # SyncType 8 - clone. - # CopyState 4 - Synchronized. + # 8 for clone. rc, job = conn.InvokeMethod( 'CreateGroupReplica', replicationService, RelationshipName=relationName, SourceGroup=srcGroupInstanceName, TargetGroup=tgtGroupInstanceName, - SyncType=self.utils.get_num(8, '16'), - WaitForCopyState=self.utils.get_num(4, '16')) + SyncType=self.utils.get_num(8, '16')) if rc != 0: rc, errordesc = self.utils.wait_for_job_complete(conn, job, diff --git a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py index 2aa8cd30c..a8a6d366b 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py @@ -16,9 +16,10 @@ import time from oslo_log import log as logging +import six from cinder import exception -from cinder.i18n import _, _LE +from cinder.i18n import _, _LE, _LW from cinder.volume.drivers.emc import emc_vmax_utils LOG = logging.getLogger(__name__) @@ -29,6 +30,7 @@ POSTGROUPTYPE = 3 EMC_ROOT = 'root/emc' THINPROVISIONINGCOMPOSITE = 32768 THINPROVISIONING = 5 +INFO_SRC_V3 = 3 class EMCVMAXProvisionV3(object): @@ -411,12 +413,9 @@ class EMCVMAXProvisionV3(object): :param slo: slo string e.g Bronze :param workload: workload string e.g DSS :param extraSpecs: additional info - :returns: maximumVolumeSize - the maximum volume size supported - :returns: minimumVolumeSize - the minimum volume size supported + :returns: supportedSizeDict """ - maximumVolumeSize = None - minimumVolumeSize = None - + supportedSizeDict = {} storagePoolCapabilityInstanceName = self._get_storage_pool_capability( conn, poolInstanceName) if storagePoolCapabilityInstanceName: @@ -426,11 +425,7 @@ class EMCVMAXProvisionV3(object): supportedSizeDict = self._get_supported_size_range_for_SLO( conn, storageConfigService, poolInstanceName, storagePoolSettingInstanceName, extraSpecs) - - maximumVolumeSize = supportedSizeDict['MaximumVolumeSize'] - minimumVolumeSize = supportedSizeDict['MinimumVolumeSize'] - - return maximumVolumeSize, minimumVolumeSize + return supportedSizeDict def activate_snap_relationship( self, conn, repServiceInstanceName, syncInstanceName, extraSpecs): @@ -578,3 +573,97 @@ class EMCVMAXProvisionV3(object): LOG.error(exceptionMsg) raise exception.VolumeBackendAPIException(data=exceptionMsg) return rc, job + + def get_srp_pool_stats(self, conn, arrayInfo): + """Get the totalManagedSpace, remainingManagedSpace. + + :param conn: the connection to the ecom server + :param arrayInfo: the array dict + :returns: totalCapacityGb + :returns: remainingCapacityGb + """ + totalCapacityGb = -1 + remainingCapacityGb = -1 + storageSystemInstanceName = self.utils.find_storageSystem( + conn, arrayInfo['SerialNumber']) + + srpPoolInstanceNames = conn.AssociatorNames( + storageSystemInstanceName, + ResultClass='Symm_SRPStoragePool') + + for srpPoolInstanceName in srpPoolInstanceNames: + poolnameStr = self.utils.get_pool_name(conn, srpPoolInstanceName) + + if six.text_type(arrayInfo['PoolName']) == ( + six.text_type(poolnameStr)): + try: + # Check that pool hasn't suddently been deleted. + srpPoolInstance = conn.GetInstance(srpPoolInstanceName) + propertiesList = srpPoolInstance.properties.items() + for properties in propertiesList: + if properties[0] == 'TotalManagedSpace': + cimProperties = properties[1] + totalManagedSpace = cimProperties.value + totalCapacityGb = self.utils.convert_bits_to_gbs( + totalManagedSpace) + elif properties[0] == 'RemainingManagedSpace': + cimProperties = properties[1] + remainingManagedSpace = cimProperties.value + remainingCapacityGb = ( + self.utils.convert_bits_to_gbs( + remainingManagedSpace)) + except Exception: + pass + remainingSLOCapacityGb = ( + self._get_remaining_slo_capacity_wlp( + conn, srpPoolInstanceName, arrayInfo, + storageSystemInstanceName['Name'])) + if remainingSLOCapacityGb != -1: + remainingCapacityGb = remainingSLOCapacityGb + else: + LOG.warning(_LW( + "Remaining capacity %(remainingCapacityGb)s " + "GBs is determined from SRP pool capacity " + "and not the SLO capacity. Performance may " + "not be what you expect."), + {'remainingCapacityGb': remainingCapacityGb}) + + return totalCapacityGb, remainingCapacityGb + + def _get_remaining_slo_capacity_wlp(self, conn, srpPoolInstanceName, + arrayInfo, systemName): + """Get the remaining SLO capacity. + + This is derived from the WLP portion of Unisphere. Please + see the SMIProvider doc and the readme doc for details. + + :param conn: the connection to the ecom server + :param srpPoolInstanceName: SRP instance name + :param arrayInfo: the array dict + :param systemName: the system name + :returns: remainingCapacityGb + """ + remainingCapacityGb = -1 + storageConfigService = ( + self.utils.find_storage_configuration_service( + conn, systemName)) + + supportedSizeDict = ( + self.get_volume_range( + conn, storageConfigService, srpPoolInstanceName, + arrayInfo['SLO'], arrayInfo['Workload'], + None)) + try: + # Information source is V3. + if supportedSizeDict['EMCInformationSource'] == INFO_SRC_V3: + remainingCapacityGb = self.utils.convert_bits_to_gbs( + supportedSizeDict['EMCRemainingSLOCapacity']) + LOG.debug("Received remaining SLO Capacity " + "%(remainingCapacityGb)s GBs for SLO " + "%(SLO)s and workload %(workload)s.", + {'remainingCapacityGb': remainingCapacityGb, + 'SLO': arrayInfo['SLO'], + 'workload': arrayInfo['Workload']}) + except KeyError: + pass + return remainingCapacityGb diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 8c092661f..41457ca35 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -500,6 +500,7 @@ class EMCVMAXUtils(object): :param conn: connection to the ecom server :param volumeInstanceName: the volume instance name + :param sgName: the storage group name :returns: foundStorageGroupInstanceName """ foundStorageGroupInstanceName = None @@ -568,8 +569,7 @@ class EMCVMAXUtils(object): conn.Associators(controllerConfigService, ResultClass='CIM_DeviceMaskingGroup')) - for storageMaskingGroupInstance in \ - storageMaskingGroupInstances: + for storageMaskingGroupInstance in storageMaskingGroupInstances: if storageGroupName == storageMaskingGroupInstance['ElementName']: # Check that it has not been deleted recently. @@ -654,242 +654,6 @@ class EMCVMAXUtils(object): return instanceName - def get_ecom_server(self, filename): - """Given the file name get the ecomPort and ecomIP from it. - - :param filename: the path and file name of the emc configuration file - :returns: ecomIp - the ecom IP address - :returns: ecomPort - the ecom port - """ - ecomIp = self._parse_from_file(filename, 'EcomServerIp') - ecomPort = self._parse_from_file(filename, 'EcomServerPort') - if ecomIp is not None and ecomPort is not None: - LOG.debug("Ecom IP: %(ecomIp)s Port: %(ecomPort)s.", - {'ecomIp': ecomIp, 'ecomPort': ecomPort}) - return ecomIp, ecomPort - else: - LOG.debug("Ecom server not found.") - return None - - def get_ecom_cred(self, filename): - """Given the filename get the ecomUser and ecomPasswd. - - :param filename: the path and filename of the emc configuration file - :returns: ecomUser - the ecom user - :returns: ecomPasswd - the ecom password - """ - ecomUser = self._parse_from_file(filename, 'EcomUserName') - ecomPasswd = self._parse_from_file(filename, 'EcomPassword') - if ecomUser is not None and ecomPasswd is not None: - return ecomUser, ecomPasswd - else: - LOG.debug("Ecom user not found.") - return None - - def get_ecom_cred_SSL(self, filename): - """Given the filename get the ecomUser and ecomPasswd. - - :param filename: the path and filename of the emc configuration file - :returns: string -- ecomUseSSL - :returns: string -- ecomCACert - :returns: string -- ecomNoVerification - """ - ecomUseSSL = self._parse_from_file(filename, 'EcomUseSSL') - ecomCACert = self._parse_from_file(filename, 'EcomCACert') - ecomNoVerification = self._parse_from_file( - filename, 'EcomNoVerification') - if ecomUseSSL is not None and ecomUseSSL == 'True': - ecomUseSSL = True - if ecomNoVerification is not None and ecomNoVerification == 'True': - ecomNoVerification = True - return ecomUseSSL, ecomCACert, ecomNoVerification - else: - ecomUseSSL = False - ecomNoVerification = False - return ecomUseSSL, ecomCACert, ecomNoVerification - - def parse_file_to_get_port_group_name(self, fileName): - """Parses a file and chooses a port group randomly. - - Given a file, parse it to get all the possible - portGroupElements and choose one randomly. - - :param fileName: the path and name of the file - :returns: string -- portGroupName - the name of the port group chosen - :raises: VolumeBackendAPIException - """ - portGroupName = None - myFile = open(fileName, 'r') - data = myFile.read() - myFile.close() - dom = minidom.parseString(data) - portGroupElements = dom.getElementsByTagName('PortGroup') - - if portGroupElements is not None and len(portGroupElements) > 0: - portGroupNames = [] - for portGroupElement in portGroupElements: - if portGroupElement.hasChildNodes(): - portGroupName = portGroupElement.childNodes[0].nodeValue - portGroupName = portGroupName.replace('\n', '') - portGroupName = portGroupName.replace('\r', '') - portGroupName = portGroupName.replace('\t', '') - portGroupName = portGroupName.strip() - if portGroupName: - portGroupNames.append(portGroupName) - - LOG.debug("portGroupNames: %(portGroupNames)s.", - {'portGroupNames': portGroupNames}) - numPortGroups = len(portGroupNames) - if numPortGroups > 0: - selectedPortGroupName = ( - portGroupNames[random.randint(0, numPortGroups - 1)]) - LOG.debug("Returning Selected Port Group: " - "%(selectedPortGroupName)s.", - {'selectedPortGroupName': selectedPortGroupName}) - return selectedPortGroupName - - # If reaches here without returning yet, raise exception. - exception_message = (_("No Port Group elements found in config file.")) - LOG.error(exception_message) - raise exception.VolumeBackendAPIException(data=exception_message) - - def _parse_from_file(self, fileName, stringToParse): - """Parse the string from XML. - - Remove newlines, tabs and trailing spaces. - - :param fileName: the path and name of the file - :param stringToParse: the name of the tag to get the value for - :returns: string -- the returned string; value of the tag - """ - retString = None - myFile = open(fileName, 'r') - data = myFile.read() - myFile.close() - dom = minidom.parseString(data) - tag = dom.getElementsByTagName(stringToParse) - if tag is not None and len(tag) > 0: - strXml = tag[0].toxml() - strXml = strXml.replace('<%s>' % stringToParse, '') - strXml = strXml.replace('\n', '') - strXml = strXml.replace('\r', '') - strXml = strXml.replace('\t', '') - retString = strXml.replace('' % stringToParse, '') - retString = retString.strip() - return retString - - def parse_fast_policy_name_from_file(self, fileName): - """Parse the fast policy name from config file. - - If it is not there, then NON FAST is assumed. - - :param fileName: the path and name of the file - :returns: fastPolicyName - the fast policy name - """ - - fastPolicyName = self._parse_from_file(fileName, 'FastPolicy') - if fastPolicyName: - LOG.debug("File %(fileName)s: Fast Policy is %(fastPolicyName)s.", - {'fileName': fileName, - 'fastPolicyName': fastPolicyName}) - return fastPolicyName - else: - LOG.info(_LI("Fast Policy not found.")) - return None - - def parse_array_name_from_file(self, fileName): - """Parse the array name from config file. - - If it is not there then there should only be one array configured to - the ecom. If there is more than one then erroneous results can occur. - - :param fileName: the path and name of the file - :returns: string -- arrayName - the array name - """ - arrayName = self._parse_from_file(fileName, 'Array') - if arrayName: - return arrayName - else: - LOG.debug("Array not found from config file.") - return None - - def parse_pool_name_from_file(self, fileName): - """Parse the pool name from config file. - - If it is not there then we will attempt to get it from extra specs. - - :param fileName: the path and name of the file - :returns: string -- poolName - the pool name - """ - poolName = self._parse_from_file(fileName, 'Pool') - if poolName: - return poolName - else: - LOG.debug("Pool not found from config file.") - return None - - def parse_slo_from_file(self, fileName): - """Parse the slo from config file. - - Please note that the string 'NONE' is returned if it is not found. - - :param fileName: the path and name of the file - :returns: string -- the slo or 'NONE' - """ - slo = self._parse_from_file(fileName, 'SLO') - if slo: - return slo - else: - LOG.debug("SLO not in config file. " - "Defaulting to NONE.") - return 'NONE' - - def parse_workload_from_file(self, fileName): - """Parse the workload from config file. - - Please note that the string 'NONE' is returned if it is not found. - - :param fileName: the path and name of the file - :returns: string -- the workload or 'NONE' - """ - workload = self._parse_from_file(fileName, 'Workload') - if workload: - return workload - else: - LOG.debug("Workload not in config file. " - "Defaulting to NONE.") - return 'NONE' - - def parse_interval_from_file(self, fileName): - """Parse the interval from config file. - - If it is not there then the default will be used. - - :param fileName: the path and name of the file - :returns: interval - the interval in seconds - """ - interval = self._parse_from_file(fileName, 'Interval') - if interval: - return interval - else: - LOG.debug("Interval not overridden, default of 10 assumed.") - return None - - def parse_retries_from_file(self, fileName): - """Parse the retries from config file. - - If it is not there then the default will be used. - - :param fileName: the path and name of the file - :returns: retries - the max number of retries - """ - retries = self._parse_from_file(fileName, 'Retries') - if retries: - return retries - else: - LOG.debug("Retries not overridden, default of 60 assumed.") - return None - def parse_pool_instance_id(self, poolInstanceId): """Given the instance Id parse the pool name and system name from it. @@ -1223,14 +987,13 @@ class EMCVMAXUtils(object): LOG.debug( "storagePoolName: %(poolName)s, storageSystemName: %(array)s.", {'poolName': storagePoolName, 'array': storageSystemName}) - storageSystemInstanceName = self.find_storageSystem(conn, - storageSystemName) - poolInstanceNames = conn.AssociatorNames( - storageSystemInstanceName, - ResultClass='EMC_VirtualProvisioningPool') + poolInstanceNames = conn.EnumerateInstanceNames( + 'EMC_VirtualProvisioningPool') for poolInstanceName in poolInstanceNames: - poolName = self._get_pool_name(conn, poolInstanceName) - if (poolName == storagePoolName): + poolName, systemName = ( + self.parse_pool_instance_id(poolInstanceName['InstanceID'])) + if (poolName == storagePoolName and + storageSystemName in systemName): # Check that the pool hasn't been recently deleted. instance = self.get_existing_instance(conn, poolInstanceName) if instance is None: @@ -1622,13 +1385,27 @@ class EMCVMAXUtils(object): :returns: foundPoolInstanceName :returns: string -- systemNameStr """ + foundPoolInstanceName = None vpoolInstanceNames = conn.AssociatorNames( storageSystemInstanceName, ResultClass='EMC_VirtualProvisioningPool') - return self._get_pool_instance_and_system_name( - conn, vpoolInstanceNames, storageSystemInstanceName, - poolNameInStr) + 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 def get_pool_and_system_name_v3( self, conn, storageSystemInstanceName, poolNameInStr): @@ -1640,41 +1417,29 @@ class EMCVMAXUtils(object): :returns: foundPoolInstanceName :returns: string -- systemNameStr """ + foundPoolInstanceName = None srpPoolInstanceNames = conn.AssociatorNames( storageSystemInstanceName, ResultClass='Symm_SRPStoragePool') - 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: + for srpPoolInstanceName in srpPoolInstanceNames: + poolInstanceID = srpPoolInstanceName['InstanceID'] # Example: SYMMETRIX-+-000196700535-+-SR-+-SRP_1 - # 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 + 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 break return foundPoolInstanceName, systemNameStr - def _get_pool_name(self, conn, poolInstanceName): - """The pool name from the instance + def get_pool_name(self, conn, poolInstanceName): + """Get the pool name from the instance :param conn: the ecom connection :param poolInstanceName: the pool instance @@ -1689,7 +1454,7 @@ class EMCVMAXUtils(object): return poolnameStr def find_storageSystem(self, conn, arrayStr): - """Find an array instance name given the array name. + """Find an array instance name by the array name. :param conn: the ecom connection :param arrayStr: the array Serial number (string) @@ -1844,28 +1609,21 @@ class EMCVMAXUtils(object): LOG.debug("metaMembers: %(members)s.", {'members': metaMembers}) return metaMembers - def get_meta_members_capacity_in_byte(self, conn, volumeInstanceNames): - """Get the capacity in byte of all meta device member volumes. + def get_meta_members_capacity_in_bit(self, conn, volumeInstanceNames): + """Get the capacity in bits 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 """ - capacitiesInByte = [] - headVolume = conn.GetInstance(volumeInstanceNames[0]) - totalSizeInByte = ( - headVolume['ConsumableBlocks'] * headVolume['BlockSize']) - volumeInstanceNames.pop(0) + capacitiesInBit = [] for volumeInstanceName in volumeInstanceNames: volumeInstance = conn.GetInstance(volumeInstanceName) numOfBlocks = volumeInstance['ConsumableBlocks'] blockSize = volumeInstance['BlockSize'] - volumeSizeInByte = numOfBlocks * blockSize - capacitiesInByte.append(volumeSizeInByte) - totalSizeInByte = totalSizeInByte - volumeSizeInByte - - capacitiesInByte.insert(0, totalSizeInByte) - return capacitiesInByte + volumeSizeInbits = numOfBlocks * blockSize + capacitiesInBit.append(volumeSizeInbits) + return capacitiesInBit def get_existing_instance(self, conn, instanceName): """Check that the instance name still exists and return the instance. @@ -2002,6 +1760,373 @@ class EMCVMAXUtils(object): LOG.warning(_LW("Cannot determine the hardware type.")) return hardwareTypeId + def _process_tag(self, element, tagName): + """Process the tag to get the value. + + :param element: the parent element + :param tagName: the tag name + :returns: nodeValue(can be None) + """ + nodeValue = None + try: + processedElement = element.getElementsByTagName(tagName)[0] + nodeValue = processedElement.childNodes[0].nodeValue + if nodeValue: + nodeValue = nodeValue.strip() + except IndexError: + pass + return nodeValue + + def _get_connection_info(self, ecomElement): + """Given the filename get the ecomUser and ecomPasswd. + + :param ecomElement: the ecom element + :returns: dict -- connargs - the connection info dictionary + :raises: VolumeBackendAPIException + """ + connargs = {} + connargs['EcomServerIp'] = ( + self._process_tag(ecomElement, 'EcomServerIp')) + connargs['EcomServerPort'] = ( + self._process_tag(ecomElement, 'EcomServerPort')) + connargs['EcomUserName'] = ( + self._process_tag(ecomElement, 'EcomUserName')) + connargs['EcomPassword'] = ( + self._process_tag(ecomElement, 'EcomPassword')) + + for k, __ in connargs.items(): + if connargs[k] is None: + exceptionMessage = (_( + "EcomServerIp, EcomServerPort, EcomUserName, " + "EcomPassword must have valid values.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # These can be None + connargs['EcomUseSSL'] = self._process_tag(ecomElement, 'EcomUseSSL') + connargs['EcomCACert'] = self._process_tag(ecomElement, 'EcomCACert') + connargs['EcomNoVerification'] = ( + self._process_tag(ecomElement, 'EcomNoVerification')) + + if connargs['EcomUseSSL'] and connargs['EcomUseSSL'] == 'True': + connargs['EcomUseSSL'] = True + if connargs['EcomNoVerification'] and ( + connargs['EcomNoVerification'] == 'True'): + connargs['EcomNoVerification'] = True + else: + connargs['EcomUseSSL'] = False + connargs['EcomNoVerification'] = False + + return connargs + + def _fill_record(self, connargs, serialNumber, poolName, + portGroup, element): + """Fill a single record. + + :param connargs: the connection info + :param serialNumber: the serial number of array + :param poolName: the poolname + :param portGroup: the portGroup + :param element: the parent element + :returns: dict -- kwargs + """ + kwargs = {} + kwargs['EcomServerIp'] = connargs['EcomServerIp'] + kwargs['EcomServerPort'] = connargs['EcomServerPort'] + kwargs['EcomUserName'] = connargs['EcomUserName'] + kwargs['EcomPassword'] = connargs['EcomPassword'] + kwargs['EcomUseSSL'] = connargs['EcomUseSSL'] + kwargs['EcomCACert'] = connargs['EcomCACert'] + kwargs['EcomNoVerification'] = connargs['EcomNoVerification'] + + slo = self._process_tag(element, 'SLO') + if slo is None: + slo = 'NONE' + kwargs['SLO'] = slo + workload = self._process_tag(element, 'Workload') + if workload is None: + workload = 'NONE' + kwargs['Workload'] = workload + fastPolicy = self._process_tag(element, 'FastPolicy') + kwargs['FastPolicy'] = fastPolicy + kwargs['SerialNumber'] = serialNumber + kwargs['PoolName'] = poolName + kwargs['PortGroup'] = portGroup + + return kwargs + + def _multi_pool_support(self, fileName): + """Multi pool support. + + + + + 10.108.246.202 + ... + + + 000198700439 + ... + + + FC_SLVR1 + ... + + + + + + + + + :param fileName: the configuration file + :returns: list + """ + myList = [] + connargs = {} + myFile = open(fileName, 'r') + data = myFile.read() + myFile.close() + dom = minidom.parseString(data) + interval = self._process_tag(dom, 'Interval') + retries = self._process_tag(dom, 'Retries') + try: + ecomElements = dom.getElementsByTagName('EcomServer') + if ecomElements and len(ecomElements) > 0: + for ecomElement in ecomElements: + connargs = self._get_connection_info(ecomElement) + arrayElements = ecomElement.getElementsByTagName('Array') + if arrayElements and len(arrayElements) > 0: + for arrayElement in arrayElements: + myList = self._get_pool_info(arrayElement, + fileName, connargs, + interval, retries, + myList) + else: + LOG.error(_LE( + "Please check your xml for format or syntax " + "errors. Please see documentation for more " + "details.")) + except IndexError: + pass + return myList + + def _single_pool_support(self, fileName): + """Single pool support. + + + 10.108.246.202 + 5988 + admin + #1Password + + OS-PORTGROUP1-PG + + 000198700439 + FC_SLVR1 + + + :param fileName: the configuration file + :returns: list + """ + myList = [] + kwargs = {} + connargs = {} + myFile = open(fileName, 'r') + data = myFile.read() + myFile.close() + dom = minidom.parseString(data) + try: + connargs = self._get_connection_info(dom) + interval = self._process_tag(dom, 'Interval') + retries = self._process_tag(dom, 'Retries') + portGroup = self._get_random_portgroup(dom) + + serialNumber = self._process_tag(dom, 'Array') + if serialNumber is None: + LOG.error(_LE( + "Array Serial Number must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + poolName = self._process_tag(dom, 'Pool') + if poolName is None: + LOG.error(_LE( + "PoolName must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + kwargs = self._fill_record( + connargs, serialNumber, poolName, portGroup, dom) + if interval: + kwargs['Interval'] = interval + if retries: + kwargs['Retries'] = retries + + myList.append(kwargs) + except IndexError: + pass + return myList + + def parse_file_to_get_array_map(self, fileName): + """Parses a file and gets array map. + + Given a file, parse it to get array and any pool(s) or + fast policy(s), SLOs, Workloads that might exist. + + :param fileName: the path and name of the file + :returns: list + """ + # Multi-pool support. + myList = self._multi_pool_support(fileName) + if len(myList) == 0: + myList = self._single_pool_support(fileName) + + return myList + + def extract_record(self, arrayInfo, pool): + """Given pool string determine the correct record. + + The poolName and the serialNumber will determine the + correct record to return in VMAX2. + The poolName, SLO and the serialNumber will determine the + correct record to return in VMAX3. + + :param arrayInfo: list of records + :param pool: e.g 'SATA_BRONZE1+000198700439' + 'SRP_1+Bronze+000198700555' + :returns: single record + """ + foundArrayInfoRec = {} + if pool: + for arrayInfoRec in arrayInfo: + if pool.count('+') == 2: + compString = ("%(slo)s+%(poolName)s+%(array)s" + % {'slo': arrayInfoRec['SLO'], + 'poolName': arrayInfoRec['PoolName'], + 'array': arrayInfoRec['SerialNumber']}) + else: + compString = ("%(poolName)s+%(array)s" + % {'poolName': arrayInfoRec['PoolName'], + 'array': arrayInfoRec['SerialNumber']}) + if compString == pool: + LOG.info(_LI( + "The pool_name from extraSpecs is %(pool)s."), + {'pool': pool}) + foundArrayInfoRec = arrayInfoRec + break + else: + foundArrayInfoRec = self._get_serial_number(arrayInfo) + + return foundArrayInfoRec + + def _get_random_portgroup(self, element): + """Get a portgroup from list of portgroup. + + Parse all available port groups under a particular + array and choose one. + + :param element: the parent element + :returns: the randomly chosen port group + :raises: VolumeBackendAPIException + """ + portGroupElements = element.getElementsByTagName('PortGroup') + if portGroupElements and len(portGroupElements) > 0: + portGroupNames = [] + for __ in portGroupElements: + portGroupName = self._process_tag( + element, 'PortGroup') + if portGroupName: + portGroupNames.append(portGroupName) + + LOG.debug("portGroupNames: %(portGroupNames)s.", + {'portGroupNames': portGroupNames}) + numPortGroups = len(portGroupNames) + if numPortGroups > 0: + selectedPortGroupName = ( + portGroupNames[random.randint(0, numPortGroups - 1)]) + LOG.debug("Returning selected PortGroup: " + "%(selectedPortGroupName)s.", + {'selectedPortGroupName': selectedPortGroupName}) + return selectedPortGroupName + + exception_message = (_("No PortGroup elements found in config file.")) + LOG.error(exception_message) + raise exception.VolumeBackendAPIException(data=exception_message) + + def _get_serial_number(self, arrayInfo): + """If we don't have a pool then we just get the serial number. + + If there is more then one serial number we must return an + error and a recommendation to edit the EMC conf file. + + :param arrayInfo: list of records + :returns: any record where serial number exists + :raises: VolumeBackendAPIException + """ + serialNumberList = [] + foundRecord = {} + + for arrayInfoRec in arrayInfo: + serialNumberList.append(arrayInfoRec['SerialNumber']) + foundRecord = arrayInfoRec + + if len(set(serialNumberList)) > 1: + # We have more than one serial number in the dict. + exception_message = (_("Multiple SerialNumbers found, when only " + "one was expected for this operation. " + "Please change your EMC config file.")) + raise exception.VolumeBackendAPIException(data=exception_message) + + return foundRecord + + def _get_pool_info(self, arrayElement, fileName, connargs, interval, + retries, myList): + """Get pool information from element. + + :param arrayElement: arrayElement + :param fileName: configuration file + :param connargs: connection arguments + :param interval: interval, can be None + :param retries: retries, can be None + :param myList: list (input) + :returns: list (output) + :raises: VolumeBackendAPIException + """ + kwargs = {} + portGroup = self._get_random_portgroup(arrayElement) + serialNumber = self._process_tag( + arrayElement, 'SerialNumber') + if serialNumber is None: + exceptionMessage = (_( + "SerialNumber must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + poolElements = arrayElement.getElementsByTagName('Pool') + if poolElements and len(poolElements) > 0: + for poolElement in poolElements: + poolName = self._process_tag(poolElement, 'PoolName') + if poolName is None: + exceptionMessage = (_( + "PoolName must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + kwargs = self._fill_record(connargs, serialNumber, + poolName, portGroup, + poolElement) + if interval: + kwargs['Interval'] = interval + if retries: + kwargs['Retries'] = retries + myList.append(kwargs) + return myList + def find_volume_by_device_id_on_array(self, conn, storageSystem, deviceID): """Find the volume by device ID on a specific array. @@ -2134,48 +2259,3 @@ 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