From 92a817708a3938b1b734d2caaa206b310996d8d0 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Sun, 8 Feb 2015 21:10:49 -0500 Subject: [PATCH] EMC VMAX driver Kilo update This patch updated the VMAX driver for the Kilo release. Main changes in this patch include the following: * Support for consistency group. * Support for VMAX V3. Implements: blueprint emc-vmax-driver-kilo-update Change-Id: I749d5f3071cd78e8ab14f3fd1711faeda7e33b77 --- cinder/tests/test_emc_vmax.py | 2137 +++++++++--- cinder/volume/drivers/emc/emc_vmax_common.py | 2931 ++++++++++++----- cinder/volume/drivers/emc/emc_vmax_fast.py | 174 +- cinder/volume/drivers/emc/emc_vmax_fc.py | 46 +- cinder/volume/drivers/emc/emc_vmax_https.py | 343 ++ cinder/volume/drivers/emc/emc_vmax_iscsi.py | 80 +- cinder/volume/drivers/emc/emc_vmax_masking.py | 917 +++--- .../volume/drivers/emc/emc_vmax_provision.py | 535 ++- .../drivers/emc/emc_vmax_provision_v3.py | 551 ++++ cinder/volume/drivers/emc/emc_vmax_utils.py | 759 ++++- 10 files changed, 6392 insertions(+), 2081 deletions(-) create mode 100644 cinder/volume/drivers/emc/emc_vmax_https.py create mode 100644 cinder/volume/drivers/emc/emc_vmax_provision_v3.py diff --git a/cinder/tests/test_emc_vmax.py b/cinder/tests/test_emc_vmax.py index f076db7f4..f492250fd 100644 --- a/cinder/tests/test_emc_vmax.py +++ b/cinder/tests/test_emc_vmax.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation, Inc. +# Copyright (c) 2012 - 2015 EMC Corporation, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -26,15 +26,16 @@ from cinder import exception from cinder.openstack.common import log as logging from cinder.openstack.common import loopingcall from cinder import test -from cinder.volume.drivers.emc.emc_vmax_common import EMCVMAXCommon -from cinder.volume.drivers.emc.emc_vmax_fast import EMCVMAXFast -from cinder.volume.drivers.emc.emc_vmax_fc import EMCVMAXFCDriver -from cinder.volume.drivers.emc.emc_vmax_iscsi import EMCVMAXISCSIDriver -from cinder.volume.drivers.emc.emc_vmax_masking import EMCVMAXMasking -from cinder.volume.drivers.emc.emc_vmax_provision import EMCVMAXProvision -from cinder.volume.drivers.emc.emc_vmax_utils import EMCVMAXUtils +from cinder.volume.drivers.emc import emc_vmax_common +from cinder.volume.drivers.emc import emc_vmax_fast +from cinder.volume.drivers.emc import emc_vmax_fc +from cinder.volume.drivers.emc import emc_vmax_iscsi +from cinder.volume.drivers.emc import emc_vmax_masking +from cinder.volume.drivers.emc import emc_vmax_provision_v3 +from cinder.volume.drivers.emc import emc_vmax_utils from cinder.volume import volume_types + LOG = logging.getLogger(__name__) CINDER_EMC_CONFIG_DIR = '/etc/cinder/' @@ -59,6 +60,14 @@ class SE_StorageHardwareID(dict): pass +class CIM_ReplicationServiceCapabilities(dict): + pass + + +class SYMM_SrpStoragePool(dict): + pass + + class SYMM_LunMasking(dict): pass @@ -79,6 +88,14 @@ class EMC_StorageHardwareID(dict): pass +class SE_ReplicationSettingData(dict): + def __init__(self, *args, **kwargs): + self['DefaultInstance'] = self.createInstance() + + def createInstance(self): + self.DesiredCopyMethodology = 0 + + class Fake_CIMProperty(): def fake_getCIMProperty(self): @@ -106,6 +123,16 @@ class Fake_CIMProperty(): cimproperty.value = False return cimproperty + def fake_getTotalManagedSpaceCIMProperty(self): + cimproperty = Fake_CIMProperty() + cimproperty.value = '20000000000' + return cimproperty + + def fake_getRemainingManagedSpaceCIMProperty(self): + cimproperty = Fake_CIMProperty() + cimproperty.value = '10000000000' + return cimproperty + def fake_getElementNameCIMProperty(self): cimproperty = Fake_CIMProperty() cimproperty.value = 'OS-myhost-MV' @@ -154,6 +181,19 @@ class FakeDB(): objectpath['DeviceID'] = volume_id return conn.GetInstance(objectpath) + def volume_get_all_by_group(self, context, group_id): + volumes = [] + volumes.append(EMCVMAXCommonData.test_source_volume) + return volumes + + def consistencygroup_get(self, context, cg_group_id): + return EMCVMAXCommonData.test_CG + + def snapshot_get_all_for_cgsnapshot(self, context, cgsnapshot_id): + snapshots = [] + snapshots.append(EMCVMAXCommonData.test_snapshot) + return snapshots + class EMCVMAXCommonData(): wwpn1 = "123456789012345" @@ -183,15 +223,16 @@ class EMCVMAXCommonData(): u'//10.10.10.10/root/emc: SE_DeviceMaskingGroup.InstanceID=' '"SYMMETRIX+000198700440+OS_default_GOLD1_SG"') storage_system = 'SYMMETRIX+000195900551' + storage_system_v3 = 'SYMMETRIX-+-000197200056' port_group = 'OS-portgroup-PG' - lunmaskctrl_id =\ + lunmaskctrl_id = \ 'SYMMETRIX+000195900551+OS-fakehost-gold-MV' - lunmaskctrl_name =\ + lunmaskctrl_name = \ 'OS-fakehost-gold-MV' - initiatorgroup_id =\ + initiatorgroup_id = \ 'SYMMETRIX+000195900551+OS-fakehost-IG' - initiatorgroup_name =\ + initiatorgroup_name = \ 'OS-fakehost-IG' initiatorgroup_creationclass = 'SE_InitiatorMaskingGroup' @@ -211,8 +252,10 @@ class EMCVMAXCommonData(): policyrule_creationclass = 'Symm_TierPolicyRule' assoctierpolicy_creationclass = 'CIM_StorageTier' storagepool_creationclass = 'Symm_VirtualProvisioningPool' + srpstoragepool_creationclass = 'Symm_SRPStoragePool' storagegroup_creationclass = 'CIM_DeviceMaskingGroup' hardwareid_creationclass = 'EMC_StorageHardwareID' + replicationgroup_creationclass = 'CIM_ReplicationGroup' storagepoolid = 'SYMMETRIX+000195900551+U+gold' storagegroupname = 'OS_default_GOLD1_SG' storagevolume_creationclass = 'EMC_StorageVolume' @@ -254,8 +297,11 @@ class EMCVMAXCommonData(): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', - 'host': 'fake-host' + 'host': 'fake-host', + 'NumberOfBlocks': 100, + 'BlockSize': 512 } + test_volume_v2 = {'name': 'vol1', 'size': 1, 'volume_name': 'vol1', @@ -268,13 +314,50 @@ class EMCVMAXCommonData(): 'volume_type_id': 'abc', 'provider_location': six.text_type(provider_location), 'status': 'available', + 'host': 'fake-host', + 'NumberOfBlocks': 100, + 'BlockSize': 512 + } + + test_volume_v3 = {'name': 'vol1', + 'size': 1, + 'volume_name': 'vol1', + 'id': 'vol1', + 'device_id': '1', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': 'abc', + 'provider_location': six.text_type(provider_location), + 'status': 'available', + 'host': 'fake-host', + 'NumberOfBlocks': 100, + 'BlockSize': 512 + } + + test_volume_CG = {'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' } + test_failed_volume = {'name': 'failed_vol', 'size': 1, 'volume_name': 'failed_vol', 'id': '4', - 'device_id': '4', + 'device_id': '1', 'provider_auth': None, 'project_id': 'project', 'display_name': 'failed_vol', @@ -298,8 +381,9 @@ class EMCVMAXCommonData(): 'volume_type_id': 'sourceid', 'display_name': 'sourceVolume', 'name': 'sourceVolume', + 'id': 'sourceVolume', + 'device_id': '1', 'volume_name': 'vmax-154326', - 'id': 'vmax-154326', 'provider_auth': None, 'project_id': 'project', 'id': '2', @@ -307,15 +391,41 @@ class EMCVMAXCommonData(): 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' + } + test_snapshot = {'name': 'myCG1', + 'id': '12345abcde', + 'status': 'available' + } + test_CG_snapshot = {'name': 'testSnap', + 'id': '12345abcde', + 'consistencygroup_id': '123456789', + 'status': 'available', + 'snapshots': [] + } location_info = {'location_info': '000195900551#silver#None', 'storage_protocol': 'ISCSI'} 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'} initiatorNames = ["123456789012345", "123456789054321"] test_ctxt = {} new_type = {} diff = {} + extra_specs = {'storagetype:pool': u'SRP_1', + 'volume_backend_name': 'V3_BE', + 'storagetype:workload': u'DSS', + 'storagetype:slo': u'Bronze', + 'storagetype:array': u'0123456789', + 'isV3': True} class FakeLookupService(): @@ -340,7 +450,12 @@ class FakeEcomConnection(): CompositeType=None, EMCNumberOfMembers=None, EMCBindElements=None, InElements=None, TargetPool=None, RequestedState=None, - GroupName=None, Type=None, InitiatorMaskingGroup=None, + ReplicationGroup=None, ReplicationType=None, + ReplicationSettingData=None, GroupName=None, Force=None, + RemoveElements=None, RelationshipName=None, + SourceGroup=None, TargetGroup=None, Goal=None, + Type=None, EMCSRP=None, EMCSLO=None, EMCWorkload=None, + EMCCollections=None, InitiatorMaskingGroup=None, DeviceMaskingGroup=None, TargetMaskingGroup=None): rc = 0L @@ -358,14 +473,6 @@ class FakeEcomConnection(): myjob['InstanceID'] = '99999' myjob['status'] = 'success' myjob['type'] = 'failed_delete_vol' - elif ElementName is None and \ - MethodName == 'CreateOrModifyCompositeElement': - rc = 0L - myjob = SE_ConcreteJob() - myjob.classname = 'SE_ConcreteJob' - myjob['InstanceID'] = '9999' - myjob['status'] = 'success' - myjob['type'] = 'vol1' if ElementName == 'failed_vol' and \ MethodName == 'CreateOrModifyElementFromStoragePool': @@ -391,7 +498,12 @@ class FakeEcomConnection(): endpoints.append(endpoint2) targetendpoints['TargetEndpoints'] = endpoints return rc, targetendpoints - + elif ReplicationType and \ + MethodName == 'GetDefaultReplicationSettingData': + rc = 0L + rsd = SE_ReplicationSettingData() + rsd['DefaultInstance'] = SE_ReplicationSettingData() + return rc, rsd job = {'Job': myjob} return rc, job @@ -424,7 +536,7 @@ class FakeEcomConnection(): elif name == 'SE_StorageHardwareID': result = self._enum_storhdwids() elif name == 'EMC_StorageSystem': - result = self._enum_storage_system() + result = self._enum_storagesystems() elif name == 'Symm_TierPolicyRule': result = self._enum_policyrules() else: @@ -442,7 +554,6 @@ class FakeEcomConnection(): return result def GetInstance(self, objectpath, LocalOnly=False): - try: name = objectpath['CreationClassName'] except KeyError: @@ -462,8 +573,18 @@ class FakeEcomConnection(): result = self._getinstance_policycapabilities(objectpath) elif name == 'SE_InitiatorMaskingGroup': result = self._getinstance_initiatormaskinggroup(objectpath) + elif name == 'CIM_InitiatorMaskingGroup': + result = self._getinstance_initiatormaskinggroup(objectpath) elif name == 'SE_StorageHardwareID': result = self._getinstance_storagehardwareid(objectpath) + elif name == 'CIM_ReplicationGroup': + result = self._getinstance_replicationgroup(objectpath) + elif name == 'Symm_SRPStoragePool': + result = self._getinstance_srpstoragepool(objectpath) + elif name == 'CIM_TargetMaskingGroup': + result = self._getinstance_targetmaskinggroup(objectpath) + elif name == 'CIM_DeviceMaskingGroup': + result = self._getinstance_devicemaskinggroup(objectpath) elif name == 'EMC_StorageHardwareID': result = self._getinstance_storagehardwareid(objectpath) elif name == 'Symm_VirtualProvisioningPool': @@ -489,7 +610,7 @@ class FakeEcomConnection(): elif ResultClass == 'CIM_DeviceMaskingGroup': result = self._assoc_storagegroup() elif ResultClass == 'CIM_StorageExtent': - result = self._enum_storage_extent() + result = self._assoc_storageextent() elif ResultClass == 'EMC_LunMaskingSCSIProtocolController': result = self._assoc_lunmaskctrls() elif ResultClass == 'CIM_TargetMaskingGroup': @@ -530,10 +651,24 @@ class FakeEcomConnection(): result = self._enum_storage_extent() elif ResultClass == 'SE_StorageHardwareID': result = self._enum_storhdwids() + elif ResultClass == 'CIM_ReplicationServiceCapabilities': + result = self._enum_repservcpbls() + elif ResultClass == 'CIM_ReplicationGroup': + result = self._enum_repgroups() + elif AssocClass == 'CIM_OrderedMemberOfCollection': + result = self._enum_storagevolumes() elif ResultClass == 'Symm_FCSCSIProtocolEndpoint': result = self._enum_fcscsiendpoint() + elif ResultClass == 'Symm_SRPStoragePool': + result = self._enum_srpstoragepool() + elif ResultClass == 'Symm_StoragePoolCapabilities': + result = self._enum_storagepoolcapabilities() + elif ResultClass == 'CIM_storageSetting': + result = self._enum_storagesettings() elif ResultClass == 'CIM_TargetMaskingGroup': result = self._assocnames_portgroup() + elif ResultClass == 'CIM_InitiatorMaskingGroup': + result = self._enum_initMaskingGroup() elif ResultClass == 'Symm_LunMaskingView': result = self._enum_maskingView() else: @@ -584,6 +719,7 @@ class FakeEcomConnection(): antecedent = SYMM_LunMasking() antecedent['CreationClassName'] = self.data.lunmask_creationclass2 antecedent['SystemName'] = self.data.storage_system + classcimproperty = Fake_CIMProperty() elementName = ( classcimproperty.fake_getElementNameCIMProperty()) @@ -657,6 +793,20 @@ class FakeEcomConnection(): ctrls.append(ctrl) return ctrls + def _assoc_maskingview(self): + assocs = [] + assoc = SYMM_LunMasking() + assoc['Name'] = 'myMaskingView' + assoc['SystemName'] = self.data.storage_system + assoc['CreationClassName'] = 'Symm_LunMaskingView' + assoc['DeviceID'] = '1234' + assoc['SystemCreationClassName'] = '1234' + assoc['ElementName'] = 'OS-fakehost-gold-I-MV' + assoc.classname = assoc['CreationClassName'] + assoc.path = assoc + assocs.append(assoc) + return assocs + # Added test for EMC_StorageVolume associators def _assoc_storagevolume(self, objectpath): assocs = [] @@ -666,31 +816,34 @@ class FakeEcomConnection(): vol = self.data.failed_delete_vol elif objectpath['type'] == 'vol1': vol = self.data.test_volume + elif objectpath['type'] == 'volInCG': + vol = self.data.test_volume_CG elif objectpath['type'] == 'appendVolume': vol = self.data.test_volume elif objectpath['type'] == 'failed_vol': vol = self.data.test_failed_volume - elif objectpath['type'] == 'TargetBaseVol': - vol = self.data.test_failed_volume else: - return None + vol = self.data.test_volume vol['DeviceID'] = vol['device_id'] assoc = self._getinstance_storagevolume(vol) + assocs.append(assoc) return assocs - def _assoc_maskingview(self): + def _assoc_storageextent(self): assocs = [] - assoc = SYMM_LunMasking() - assoc['Name'] = 'myMaskingView' + assoc = CIM_StorageExtent() + assoc['Name'] = 'myStorageExtent' assoc['SystemName'] = self.data.storage_system - assoc['CreationClassName'] = 'Symm_LunMaskingView' - assoc['DeviceID'] = '1234' - assoc['SystemCreationClassName'] = '1234' - assoc['ElementName'] = 'OS-fakehost-gold-I-MV' + assoc['CreationClassName'] = 'CIM_StorageExtent' assoc.classname = assoc['CreationClassName'] assoc.path = assoc + classcimproperty = Fake_CIMProperty() + isConcatenatedcimproperty = ( + classcimproperty.fake_getIsCompositeCIMProperty()) + properties = {u'IsConcatenated': isConcatenatedcimproperty} + assoc.properties = properties assocs.append(assoc) return assocs @@ -740,6 +893,7 @@ class FakeEcomConnection(): foundinstance = None else: foundinstance = instance + return foundinstance def _getinstance_lunmask(self): @@ -777,6 +931,41 @@ class FakeEcomConnection(): pool['EMCSubscribedCapacity'] = self.data.subscribedcapacity_bits return pool + def _getinstance_replicationgroup(self, objectpath): + replicationgroup = {} + replicationgroup['CreationClassName'] = ( + self.data.replicationgroup_creationclass) + replicationgroup['ElementName'] = '1234bcde' + return replicationgroup + + def _getinstance_srpstoragepool(self, objectpath): + srpstoragepool = SYMM_SrpStoragePool() + srpstoragepool['CreationClassName'] = ( + self.data.srpstoragepool_creationclass) + + classcimproperty = Fake_CIMProperty() + totalManagedSpace = ( + classcimproperty.fake_getTotalManagedSpaceCIMProperty()) + remainingManagedSpace = ( + classcimproperty.fake_getRemainingManagedSpaceCIMProperty()) + properties = {u'TotalManagedSpace': totalManagedSpace, + u'RemainingManagedSpace': remainingManagedSpace} + srpstoragepool.properties = properties + return srpstoragepool + + def _getinstance_targetmaskinggroup(self, objectpath): + targetmaskinggroup = CIM_TargetMaskingGroup() + targetmaskinggroup['CreationClassName'] = 'CIM_TargetMaskingGroup' + targetmaskinggroup['ElementName'] = self.data.port_group + targetmaskinggroup.path = targetmaskinggroup + return targetmaskinggroup + + def _getinstance_devicemaskinggroup(self, objectpath): + targetmaskinggroup = {} + targetmaskinggroup['CreationClassName'] = 'CIM_DeviceMaskingGroup' + targetmaskinggroup['ElementName'] = 'OS_default_GOLD1_SG' + return targetmaskinggroup + def _getinstance_unit(self, objectpath): unit = {} @@ -830,7 +1019,7 @@ class FakeEcomConnection(): conf_services = [] conf_service = {} conf_service['SystemName'] = self.data.storage_system - conf_service['CreationClassName'] =\ + conf_service['CreationClassName'] = \ self.data.stconf_service_creationclass conf_services.append(conf_service) return conf_services @@ -839,7 +1028,7 @@ class FakeEcomConnection(): conf_services = [] conf_service = {} conf_service['SystemName'] = self.data.storage_system - conf_service['CreationClassName'] =\ + conf_service['CreationClassName'] = \ self.data.ctrlconf_service_creationclass conf_services.append(conf_service) return conf_services @@ -848,7 +1037,7 @@ class FakeEcomConnection(): comp_services = [] comp_service = {} comp_service['SystemName'] = self.data.storage_system - comp_service['CreationClassName'] =\ + comp_service['CreationClassName'] = \ self.data.elementcomp_service_creationclass comp_services.append(comp_service) return comp_services @@ -857,7 +1046,7 @@ class FakeEcomConnection(): reloc_services = [] reloc_service = {} reloc_service['SystemName'] = self.data.storage_system - reloc_service['CreationClassName'] =\ + reloc_service['CreationClassName'] = \ self.data.storreloc_service_creationclass reloc_services.append(reloc_service) return reloc_services @@ -866,7 +1055,7 @@ class FakeEcomConnection(): replic_services = [] replic_service = {} replic_service['SystemName'] = self.data.storage_system - replic_service['CreationClassName'] =\ + replic_service['CreationClassName'] = \ self.data.replication_service_creationclass replic_services.append(replic_service) return replic_services @@ -874,7 +1063,7 @@ class FakeEcomConnection(): def _enum_pools(self): pools = [] pool = {} - pool['InstanceID'] = self.data.storage_system + '+U+' +\ + pool['InstanceID'] = self.data.storage_system + '+U+' + \ self.data.storage_type pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' pool['ElementName'] = 'gold' @@ -884,7 +1073,7 @@ class FakeEcomConnection(): def _enum_pool_details(self): pools = [] pool = {} - pool['InstanceID'] = self.data.storage_system + '+U+' +\ + pool['InstanceID'] = self.data.storage_system + '+U+' + \ self.data.storage_type pool['CreationClassName'] = 'Symm_VirtualProvisioningPool' pool['TotalManagedSpace'] = 12345678 @@ -898,9 +1087,12 @@ class FakeEcomConnection(): vol = EMC_StorageVolume() vol['name'] = self.data.test_volume['name'] vol['CreationClassName'] = 'Symm_StorageVolume' - vol['ElementName'] = self.data.test_volume['name'] - vol['DeviceID'] = self.data.test_volume['id'] + vol['ElementName'] = self.data.test_volume['id'] + vol['DeviceID'] = self.data.test_volume['device_id'] + vol['Id'] = self.data.test_volume['id'] vol['SystemName'] = self.data.storage_system + vol['NumberOfBlocks'] = self.data.test_volume['NumberOfBlocks'] + vol['BlockSize'] = self.data.test_volume['BlockSize'] # Added vol to vol.path vol['SystemCreationClassName'] = 'Symm_StorageSystem' @@ -940,7 +1132,7 @@ class FakeEcomConnection(): # 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.path.classname = \ failed_delete_vol['CreationClassName'] vols.append(failed_delete_vol) @@ -953,7 +1145,7 @@ class FakeEcomConnection(): # Added vol to vol.path failed_vol['SystemCreationClassName'] = 'Symm_StorageSystem' failed_vol.path = failed_vol - failed_vol.path.classname =\ + failed_vol.path.classname = \ failed_vol['CreationClassName'] name_failed = {} @@ -981,14 +1173,6 @@ class FakeEcomConnection(): initatorgroups.append(initatorgroup) return initatorgroups - def _enum_storage_system(self): - storagesystems = [] - storagesystem = {} - storagesystem['SystemName'] = self.data.storage_system - storagesystem['Name'] = self.data.storage_system - storagesystems.append(storagesystem) - return storagesystems - def _enum_storage_extent(self): storageExtents = [] storageExtent = CIM_StorageExtent() @@ -1020,6 +1204,7 @@ class FakeEcomConnection(): hostedservice['CreationClassName'] = ( self.data.hostedservice_creationclass) hostedservice['SystemName'] = self.data.storage_system + hostedservice['Name'] = self.data.storage_system hostedservices.append(hostedservice) return hostedservices @@ -1066,6 +1251,49 @@ class FakeEcomConnection(): storagepools.append(storagepool) return storagepools + def _enum_srpstoragepool(self): + storagepools = [] + storagepool = {} + storagepool['CreationClassName'] = ( + self.data.srpstoragepool_creationclass) + storagepool['InstanceID'] = 'SYMMETRIX-+-000197200056-+-SRP_1' + storagepool['ElementName'] = 'SRP_1' + storagepools.append(storagepool) + return storagepools + + def _enum_storagepoolcapabilities(self): + storagepoolcaps = [] + storagepoolcap = {} + storagepoolcap['CreationClassName'] = 'Symm_StoragePoolCapabilities' + storagepoolcap['InstanceID'] = 'SYMMETRIX-+-000197200056-+-SRP_1' + storagepoolcaps.append(storagepoolcap) + return storagepoolcaps + + def _enum_storagesettings(self): + storagesettings = [] + storagesetting = {} + storagesetting['CreationClassName'] = 'CIM_StoragePoolSetting' + storagesetting['InstanceID'] = ('SYMMETRIX-+-000197200056-+-SBronze:' + 'NONE-+-F-+-0-+-SR-+-SRP_1') + storagesettings.append(storagesetting) + return storagesettings + + def _enum_targetMaskingGroup(self): + targetMaskingGroups = [] + targetMaskingGroup = {} + targetMaskingGroup['CreationClassName'] = 'CIM_TargetMaskingGroup' + targetMaskingGroup['ElementName'] = self.data.port_group + targetMaskingGroups.append(targetMaskingGroup) + return targetMaskingGroups + + def _enum_initMaskingGroup(self): + initMaskingGroups = [] + initMaskingGroup = {} + initMaskingGroup['CreationClassName'] = 'CIM_InitiatorMaskingGroup' + initMaskingGroup['ElementName'] = 'myInitGroup' + initMaskingGroups.append(initMaskingGroup) + return initMaskingGroups + def _enum_storagegroup(self): storagegroups = [] storagegroup = {} @@ -1100,6 +1328,30 @@ class FakeEcomConnection(): storhdwids.append(hdwid) return storhdwids + def _enum_storagesystems(self): + storagesystems = [] + storagesystem = {} + storagesystem['SystemName'] = self.data.storage_system + storagesystem['Name'] = self.data.storage_system + storagesystems.append(storagesystem) + return storagesystems + + def _enum_repservcpbls(self): + repservcpbls = [] + servcpbl = CIM_ReplicationServiceCapabilities() + servcpbl['CreationClassName'] = 'Symm_ReplicationServiceCapabilities' + servcpbl['InstanceID'] = self.data.storage_system + repservcpbls.append(servcpbl) + return repservcpbls + + def _enum_repgroups(self): + repgroups = [] + repgroup = {} + repgroup['CreationClassName'] = ( + self.data.replicationgroup_creationclass) + repgroups.append(repgroup) + return repgroups + def _enum_fcscsiendpoint(self): wwns = [] wwn = {} @@ -1107,6 +1359,14 @@ class FakeEcomConnection(): wwns.append(wwn) return wwns + def _enum_maskingView(self): + maskingViews = [] + maskingView = {} + maskingView['CreationClassName'] = 'Symm_LunMaskingView' + maskingView['ElementName'] = 'myMaskingView' + maskingViews.append(maskingView) + return maskingViews + def _enum_portgroup(self): portgroups = [] portgroup = {} @@ -1116,14 +1376,6 @@ class FakeEcomConnection(): portgroups.append(portgroup) return portgroups - def _enum_maskingView(self): - maskingViews = [] - maskingView = {} - maskingView['CreationClassName'] = 'Symm_LunMaskingView' - maskingView['ElementName'] = 'myMaskingView' - maskingViews.append(maskingView) - return maskingViews - def _default_enum(self): names = [] name = {} @@ -1149,20 +1401,23 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): configuration.cinder_emc_config_file = self.config_file_path configuration.config_group = 'ISCSINoFAST' - self.stubs.Set(EMCVMAXISCSIDriver, 'smis_do_iscsi_discovery', + self.stubs.Set(emc_vmax_iscsi.EMCVMAXISCSIDriver, + 'smis_do_iscsi_discovery', self.fake_do_iscsi_discovery) - self.stubs.Set(EMCVMAXCommon, '_get_ecom_connection', + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) instancename = FakeCIMInstanceName() - self.stubs.Set(EMCVMAXUtils, 'get_instance_name', + 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 = EMCVMAXISCSIDriver(configuration=configuration) + driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) driver.db = FakeDB() self.driver = driver - self.driver.utils = EMCVMAXUtils(object) + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) def create_fake_config_file_no_fast(self): @@ -1226,8 +1481,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): doc.writexml(f) f.close() - # Create XML config file with newlines and whitespaces - # Bug #1364232 + # 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 @@ -1261,6 +1515,9 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def fake_sleep(self, seconds): return + def fake_is_v3(self, conn, serialNumber): + return False + def test_wait_for_job_complete(self): myjob = SE_ConcreteJob() myjob.classname = 'SE_ConcreteJob' @@ -1272,7 +1529,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): conn = self.fake_ecom_connection() self.driver.utils._is_job_finished = mock.Mock( - return_value = True) + return_value=True) rc = self.driver.utils._wait_for_job_complete(conn, myjob) self.assertIsNone(rc) self.driver.utils._is_job_finished.assert_called_once_with( @@ -1297,7 +1554,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): conn = self.fake_ecom_connection() self.driver.utils._is_sync_complete = mock.Mock( - return_value = True) + return_value=True) rc = self.driver.utils.wait_for_sync(conn, mysync) self.assertIsNone(rc) self.driver.utils._is_sync_complete.assert_called_once_with( @@ -1374,6 +1631,27 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.common.utils.process_exception_args, arg, instancename3) + # Tests removal of last volume in a storage group V2 + def test_remove_and_reset_members(self): + fastPolicyName = 'gold' + isV3 = False + conn = self.fake_ecom_connection() + controllerConfigService = ( + self.driver.utils.find_controller_configuration_service( + conn, self.data.storage_system)) + volumeInstanceName = ( + conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + volumeInstance = conn.GetInstance(volumeInstanceName) + volumeName = "last-Vol" + self.driver.common.masking.get_devices_from_storage_group = mock.Mock( + return_value=['one_value']) + self.driver.common.masking.utils.get_existing_instance = mock.Mock( + return_value=None) + + self.driver.common.masking.remove_and_reset_members( + conn, controllerConfigService, volumeInstance, + fastPolicyName, volumeName, isV3) + # Bug 1393555 - masking view has been deleted by another process. def test_find_maskingview(self): conn = self.fake_ecom_connection() @@ -1633,41 +1911,52 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): def test_get_volume_stats_1364232(self): self.create_fake_config_file_1364232() - self.assertEqual('000198700439', - self.driver.utils.parse_array_name_from_file( - self.config_file_1364232)) - self.assertEqual('FC_SLVR1', - self.driver.utils.parse_pool_name_from_file( - self.config_file_1364232)) - self.assertEqual('SILVER1', - self.driver.utils.parse_fast_policy_name_from_file( - self.config_file_1364232)) - self.assertIn('OS-PORTGROUP', - self.driver.utils.parse_file_to_get_port_group_name( - self.config_file_1364232)) + self.assertEqual( + '000198700439', + self.driver.utils.parse_array_name_from_file( + self.config_file_1364232)) + self.assertEqual( + 'FC_SLVR1', + self.driver.utils.parse_pool_name_from_file( + self.config_file_1364232)) + self.assertEqual( + 'SILVER1', + self.driver.utils.parse_fast_policy_name_from_file( + self.config_file_1364232)) + self.assertTrue( + 'OS-PORTGROUP' in + self.driver.utils.parse_file_to_get_port_group_name( + self.config_file_1364232)) bExists = os.path.exists(self.config_file_1364232) if bExists: os.remove(self.config_file_1364232) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'is_tiering_policy_enabled', return_value=False) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_pool_capacities', return_value=(1234, 1200)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_array_name_from_file', return_value="123456789") - def test_get_volume_stats_no_fast(self, mock_storage_system, + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'isArrayV3', + return_value=False) + def test_get_volume_stats_no_fast(self, + mock_storage_system, mock_is_fast_enabled, - mock_capacity, mock_array): + mock_capacity, + mock_array, + mock_is_v3): self.driver.get_volume_stats(True) @mock.patch.object( @@ -1675,7 +1964,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_create_volume_no_fast_success( @@ -1688,7 +1977,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value={'storagetype: stripedmetacount': '4', 'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_create_volume_no_fast_striped_success( @@ -1700,7 +1989,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_volume_in_CG_no_fast_success( + self, _mock_volume_type, mock_storage_system): + self.driver.create_volume(self.data.test_volume_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_delete_volume_no_fast_success( @@ -1732,7 +2033,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] =\ + keys['SystemCreationClassName'] = \ notfound_delete_vol['SystemCreationClassName'] name['keybindings'] = keys @@ -1743,15 +2044,11 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - '_wait_for_job_complete', - return_value=None) def test_delete_volume_failed( - self, _mock_volume_type, mock_storage_system, mock_wait): + self, _mock_volume_type, mock_storage_system): self.driver.create_volume(self.data.failed_delete_vol) self.assertRaises(exception.VolumeBackendAPIException, self.driver.delete_volume, @@ -1762,22 +2059,21 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value='value') + emc_vmax_common.EMCVMAXCommon, + 'find_device_number', + return_value={'hostlunid': 1, + 'storagesystem': EMCVMAXCommonData.storage_system}) @mock.patch.object( - EMCVMAXMasking, - '_check_adding_volume_to_storage_group', - return_value=None) - def test_map_new_masking_view_no_fast_success(self, - mock_check, - mock_storage_group, - mock_wrap_group, - mock_volume_type): + emc_vmax_common.EMCVMAXCommon, + '_is_same_host', + return_value=True) + def test_already_mapped_no_fast_success( + self, _mock_volume_type, mock_wrap_group, mock_wrap_device, + mock_is_same_host): self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -1786,33 +2082,55 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, - 'find_device_number', - return_value={'hostlunid': 1, - 'storagesystem': EMCVMAXCommonData.storage_system}) - @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storage_masking_group', return_value='value') @mock.patch.object( - EMCVMAXCommon, + emc_vmax_masking.EMCVMAXMasking, + '_check_adding_volume_to_storage_group', + return_value=None) + def test_map_new_masking_view_no_fast_success( + self, _mock_volume_type, mock_wrap_group, + mock_storage_group, mock_add_volume): + self.driver.initialize_connection(self.data.test_volume, + self.data.connector) + + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + '_check_adding_volume_to_storage_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, '_is_same_host', return_value=False) @mock.patch.object( - EMCVMAXMasking, - '_check_adding_volume_to_storage_group', + emc_vmax_utils.EMCVMAXUtils, + 'find_storage_masking_group', + return_value='value') + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + 'find_device_number', + return_value={'hostlunid': 1, + 'storagesystem': EMCVMAXCommonData.storage_system}) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + '_wrap_get_storage_group_from_volume', return_value=None) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) def test_map_live_migration_no_fast_success(self, - mock_check, - mock_same_host, - mock_storage_group, - mock_wrap_device, + _mock_volume_type, mock_wrap_group, - mock_volume_type): + mock_wrap_device, + mock_storage_group, + mock_same_host, + mock_check): self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -1821,32 +2139,33 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, - 'find_device_number', - return_value={'hostlunid': 1, - 'storagesystem': EMCVMAXCommonData.storage_system}) + emc_vmax_masking.EMCVMAXMasking, + '_find_masking_view', + return_value='value') @mock.patch.object( - EMCVMAXCommon, - '_is_same_host', - return_value=True) - def test_already_mapped_no_fast_success(self, - mock_same_host, - mock_wrap_device, - mock_wrap_group, - mock_volume_type): + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='value') + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + '_get_initiator_group_from_masking_view', + return_value='value') + def test_map_existing_masking_view_no_fast_success( + self, _mock_volume_type, mock_wrap_group, mock_storage_group, + mock_initiator_group, mock_ig_from_mv): self.driver.initialize_connection(self.data.test_volume, self.data.connector) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, 'find_device_number', return_value={'storagesystem': EMCVMAXCommonData.storage_system}) def test_map_no_fast_failed(self, mock_wrap_group, mock_wrap_device): @@ -1860,29 +2179,20 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storage_masking_group', return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_no_fast_success(self, mock_volume_type, - mock_storage_group): - - self.driver.terminate_connection( - self.data.test_volume, self.data.connector) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXUtils, 'find_storage_system', - return_value={'Name': EMCVMAXCommonData.storage_system}) + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='myInitGroup') @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_no_fast_last_volume_success( - self, mock_volume_type, - mock_storage_system, mock_storage_group): + emc_vmax_masking.EMCVMAXMasking, + 'get_initiator_group_from_masking_view', + return_value='myInitGroup') + def test_detach_no_fast_success( + self, mock_volume_type, mock_storage_group, + mock_ig, mock_igc): self.driver.terminate_connection( self.data.test_volume, self.data.connector) @@ -1891,7 +2201,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_size', return_value='2147483648') def test_extend_volume_no_fast_success( @@ -1905,7 +2215,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): return_value={'storagetype: stripedmetacount': '4', 'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'check_if_volume_is_extendable', return_value='False') def test_extend_volume_striped_no_fast_failed( @@ -1925,24 +2235,20 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_snapshot_different_sizes_meta_no_fast_success( self, mock_volume_type, mock_volume, - mock_meta, mock_size, mock_pool, mock_wait): + mock_meta, mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" common = self.driver.common volumeDict = {'classname': u'Symm_StorageVolume', @@ -1964,24 +2270,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', + emc_vmax_utils.EMCVMAXUtils, + 'find_sync_sv_by_target', return_value=(None, None)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567]) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_volume_from_same_size_meta_snapshot( - self, mock_volume_type, mock_sync_sv, mock_meta, - mock_size, mock_wait): + self, mock_volume_type, mock_sync_sv, mock_meta, mock_size): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.create_volume_from_snapshot( self.data.test_volume, self.data.test_volume) @@ -1991,7 +2292,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume_from_snapshot, self.data.test_volume, - EMCVMAXCommonData.test_source_volume) + self.data.test_volume) @mock.patch.object( volume_types, @@ -2002,20 +2303,16 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=None) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_clone_simple_volume_no_fast_success( self, mock_volume_type, mock_volume, mock_sync_sv, - mock_simple_volume, mock_wait): + mock_simple_volume): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.create_cloned_volume(self.data.test_volume, EMCVMAXCommonData.test_source_volume) @@ -2040,27 +2337,107 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_pool_instance_id', return_value=('silver', 'SYMMETRIX+000195900551')) - def test_retype_volume_no_fast_success( - self, _mock_volume_type, mock_values): + def test_retype_volume_no_fast_success(self, _mock_volume_type, + mock_values): self.driver.retype( self.data.test_ctxt, self.data.test_volume, self.data.new_type, self.data.diff, self.data.test_host) - def test_check_for_setup_error(self): - self.driver.configuration.iscsi_ip_address = '1.1.1.1' - self.driver.check_for_setup_error() - self.driver.configuration.iscsi_ip_address = None - self.assertRaises(exception.InvalidInput, - self.driver.check_for_setup_error) - - def _cleanup(self): - bExists = os.path.exists(self.config_file_path) - if bExists: - os.remove(self.config_file_path) - shutil.rmtree(self.tempdir) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_CG_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + def test_delete_CG_no_volumes_no_fast_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( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_CG_with_volumes_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=()) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value="") + def test_create_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members, + _mock_rg): + self.driver.create_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage): + self.driver.delete_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @@ -2080,16 +2457,19 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): configuration.safe_get.return_value = 'ISCSIFAST' configuration.config_group = 'ISCSIFAST' - self.stubs.Set(EMCVMAXISCSIDriver, 'smis_do_iscsi_discovery', + self.stubs.Set(emc_vmax_iscsi.EMCVMAXISCSIDriver, + 'smis_do_iscsi_discovery', self.fake_do_iscsi_discovery) - self.stubs.Set(EMCVMAXCommon, '_get_ecom_connection', + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) instancename = FakeCIMInstanceName() - self.stubs.Set(EMCVMAXUtils, 'get_instance_name', + self.stubs.Set(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', instancename.fake_getinstancename) self.stubs.Set(time, 'sleep', self.fake_sleep) - driver = EMCVMAXISCSIDriver(configuration=configuration) + 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 @@ -2173,29 +2553,35 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): def fake_sleep(self, seconds): return + def fake_is_v3(self, conn, serialNumber): + return False + @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'is_tiering_policy_enabled', return_value=True) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_tier_policy_by_name', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', return_value=(1234, 1200)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_array_name_from_file', return_value="123456789") - def test_get_volume_stats_fast(self, mock_storage_system, + def test_get_volume_stats_fast(self, + mock_storage_system, mock_is_fast_enabled, - mock_get_policy, mock_capacity, mock_array): + mock_get_policy, + mock_capacity, + mock_array): self.driver.get_volume_stats(True) @mock.patch.object( @@ -2203,11 +2589,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_create_volume_fast_success( @@ -2220,11 +2606,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value={'storagetype: stripedmetacount': '4', 'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_create_volume_fast_striped_success( @@ -2236,7 +2622,23 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_fast.EMCVMAXFast, + 'get_pool_associated_to_policy', + return_value=1) + def test_create_volume_in_CG_fast_success( + self, _mock_volume_type, mock_storage_system, mock_pool_policy): + self.driver.create_volume(self.data.test_volume_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) def test_delete_volume_fast_success( @@ -2253,7 +2655,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) def test_delete_volume_fast_notfound( @@ -2271,7 +2673,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] =\ + keys['SystemCreationClassName'] = \ notfound_delete_vol['SystemCreationClassName'] name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' @@ -2283,15 +2685,15 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_delete_volume_fast_failed( @@ -2307,29 +2709,30 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, 'find_device_number', return_value={'hostlunid': 1, 'storagesystem': EMCVMAXCommonData.storage_system}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_is_same_host', return_value=True) - def test_map_fast_success(self, mock_same_host, mock_wrap_device, - mock_wrap_group, mock_volume_type): + def test_already_mapped_fast_success( + self, _mock_volume_type, mock_wrap_group, mock_wrap_device, + mock_is_same_host): self.driver.initialize_connection(self.data.test_volume, self.data.connector) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, 'find_device_number', return_value={'storagesystem': EMCVMAXCommonData.storage_system}) def test_map_fast_failed(self, mock_wrap_group, mock_wrap_device): @@ -2343,29 +2746,20 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storage_masking_group', return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_fast_success(self, mock_volume_type, - mock_storage_group): - - self.driver.terminate_connection( - self.data.test_volume, self.data.connector) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSIFAST'}) - @mock.patch.object( - EMCVMAXUtils, 'find_storage_system', - return_value={'Name': EMCVMAXCommonData.storage_system}) + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='myInitGroup') @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - def test_detach_fast_last_volume_success( - self, mock_volume_type, - mock_storage_system, mock_storage_group): + emc_vmax_masking.EMCVMAXMasking, + 'get_initiator_group_from_masking_view', + return_value='myInitGroup') + def test_detach_fast_success( + self, mock_volume_type, mock_storage_group, + mock_ig, mock_igc): self.driver.terminate_connection( self.data.test_volume, self.data.connector) @@ -2374,7 +2768,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_size', return_value='2147483648') def test_extend_volume_fast_success( @@ -2387,7 +2781,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'check_if_volume_is_extendable', return_value='False') def test_extend_volume_striped_fast_failed( @@ -2407,28 +2801,24 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_snapshot_different_sizes_meta_fast_success( - self, mock_volume_type, mock_volume, mock_meta, - mock_size, mock_pool, mock_policy, mock_wait): + self, mock_volume_type, mock_volume, + mock_meta, mock_size, mock_pool, mock_policy): self.data.test_volume['volume_name'] = "vmax-1234567" common = self.driver.common @@ -2453,31 +2843,22 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', + emc_vmax_utils.EMCVMAXUtils, + 'find_sync_sv_by_target', return_value=(None, None)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567]) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_volume_from_same_size_meta_snapshot( - self, mock_volume_type, mock_sync_sv, mock_meta, - mock_size, mock_wait): + self, mock_volume_type, mock_sync_sv, mock_meta, mock_size): self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.common.utils.find_storage_configuration_service = ( - mock.Mock(return_value=EMCVMAXCommonData.storage_system)) - self.driver.common._get_or_create_default_storage_group = ( - mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) - self.driver.common.fast.is_volume_in_default_SG = ( - mock.Mock(return_value=True)) + common = self.driver.common + common.fast.is_volume_in_default_SG = mock.Mock(return_value=True) self.driver.create_volume_from_snapshot( self.data.test_volume, self.data.test_volume) @@ -2487,25 +2868,16 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value={'volume_backend_name': 'ISCSIFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_replication_service', return_value=None) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', + emc_vmax_utils.EMCVMAXUtils, + 'find_sync_sv_by_target', return_value=(None, None)) - @mock.patch.object( - EMCVMAXUtils, - 'get_volume_meta_head', - return_value=None) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_volume_from_snapshot_fast_failed( - self, mock_type, mock_rep_service, mock_sync_sv, - mock_meta, mock_wait): - + self, mock_volume_type, + mock_rep_service, mock_sync_sv): self.data.test_volume['volume_name'] = "vmax-1234567" self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume_from_snapshot, @@ -2521,25 +2893,17 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=None) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_clone_simple_volume_fast_success( self, mock_volume_type, mock_volume, mock_sync_sv, - mock_simple_volume, mock_wait): + mock_simple_volume): self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.common.utils.find_storage_configuration_service = ( - mock.Mock(return_value=EMCVMAXCommonData.storage_system)) - self.driver.common._get_or_create_default_storage_group = ( - mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) self.driver.common.fast.is_volume_in_default_SG = ( mock.Mock(return_value=True)) self.driver.create_cloned_volume(self.data.test_volume, @@ -2554,28 +2918,24 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_clone_fast_failed( - self, mock_volume_type, mock_vol, mock_policy, mock_meta, - mock_size, mock_pool, mock_wait): + self, mock_volume_type, mock_vol, + mock_policy, mock_meta, mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.common._modify_and_get_composite_volume_instance = ( mock.Mock(return_value=(1L, None))) @@ -2597,11 +2957,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_pool_instance_id', return_value=('silver', 'SYMMETRIX+000195900551')) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) def test_retype_volume_fast_success( @@ -2610,6 +2970,93 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): self.data.test_ctxt, self.data.test_volume, self.data.new_type, self.data.diff, self.data.test_host) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_CG_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + def test_delete_CG_no_volumes_fast_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( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_CG_with_volumes_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=()) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value="") + def test_create_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members, + _mock_rg): + self.driver.create_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage): + self.driver.delete_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -2633,19 +3080,22 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): configuration.safe_get.return_value = 'FCNoFAST' configuration.config_group = 'FCNoFAST' - self.stubs.Set(EMCVMAXCommon, '_get_ecom_connection', + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) instancename = FakeCIMInstanceName() - self.stubs.Set(EMCVMAXUtils, 'get_instance_name', + 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 = EMCVMAXFCDriver(configuration=configuration) + 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_no_fast(self): @@ -2716,27 +3166,35 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): def fake_sleep(self, seconds): return + def fake_is_v3(self, conn, serialNumber): + return False + @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'is_tiering_policy_enabled', return_value=False) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_pool_capacities', return_value=(1234, 1200)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_array_name_from_file', return_value="123456789") + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'isArrayV3', + return_value=False) def test_get_volume_stats_no_fast(self, mock_storage_system, mock_is_fast_enabled, mock_capacity, - mock_array): + mock_array, + mock_is_v3): self.driver.get_volume_stats(True) @mock.patch.object( @@ -2744,7 +3202,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_create_volume_no_fast_success( @@ -2757,7 +3215,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): return_value={'storagetype: stripedmetacount': '4', 'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_create_volume_no_fast_striped_success( @@ -2769,7 +3227,19 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_volume_in_CG_no_fast_success( + self, _mock_volume_type, mock_storage_system): + self.driver.create_volume(self.data.test_volume_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) def test_delete_volume_no_fast_success( @@ -2799,7 +3269,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] =\ + keys['SystemCreationClassName'] = \ notfound_delete_vol['SystemCreationClassName'] name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' @@ -2811,15 +3281,11 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - '_wait_for_job_complete', - return_value=None) def test_delete_volume_failed( - self, _mock_volume_type, mock_storage_system, mock_wait): + self, _mock_volume_type, mock_storage_system): self.driver.create_volume(self.data.failed_delete_vol) self.assertRaises(exception.VolumeBackendAPIException, self.driver.delete_volume, @@ -2831,20 +3297,15 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCNoFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, 'get_masking_view_from_storage_group', return_value=EMCVMAXCommonData.lunmaskctrl_name) @mock.patch.object( - EMCVMAXProvision, - '_find_new_storage_group', - return_value='Any') - @mock.patch.object( - EMCVMAXMasking, - '_check_adding_volume_to_storage_group', - return_value=None) + emc_vmax_common.EMCVMAXCommon, + '_is_same_host', + return_value=True) def test_map_lookup_service_no_fast_success( - self, mock_add_check, mock_new_sg, - mock_maskingview, mock_volume_type): + self, _mock_volume_type, mock_maskingview, mock_is_same_host): self.data.test_volume['volume_name'] = "vmax-1234567" common = self.driver.common common.get_target_wwns_from_masking_view = mock.Mock( @@ -2871,7 +3332,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCNoFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, 'find_device_number', return_value={'Name': "0001"}) def test_map_no_fast_failed(self, mock_wrap_group, mock_maskingview): @@ -2883,26 +3344,21 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST', - 'FASTPOLICY': 'FC_GOLD1'}) + return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, 'get_masking_view_by_volume', return_value=EMCVMAXCommonData.lunmaskctrl_name) - def test_detach_no_fast_success(self, mock_volume_type, mock_maskingview): - self.driver.terminate_connection(self.data.test_volume, - self.data.connector) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='myInitGroup') @mock.patch.object( - EMCVMAXMasking, - 'get_masking_view_by_volume', - return_value=EMCVMAXCommonData.lunmaskctrl_name) + emc_vmax_masking.EMCVMAXMasking, + 'get_initiator_group_from_masking_view', + return_value='myInitGroup') def test_detach_no_fast_last_volume_success( - self, mock_volume_type, mock_mv): + self, mock_volume_type, mock_mv, mock_ig, mock_igc): self.driver.terminate_connection(self.data.test_source_volume, self.data.connector) @@ -2911,7 +3367,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_size', return_value='2147483648') def test_extend_volume_no_fast_success(self, _mock_volume_type, @@ -2924,7 +3380,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'check_if_volume_is_extendable', return_value='False') def test_extend_volume_striped_no_fast_failed( @@ -2948,7 +3404,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCNoFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_pool_instance_id', return_value=('silver', 'SYMMETRIX+000195900551')) def test_retype_volume_no_fast_success( @@ -2957,6 +3413,125 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): self.data.test_ctxt, self.data.test_volume, self.data.new_type, self.data.diff, self.data.test_host) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_CG_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + def test_delete_CG_no_volumes_no_fast_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( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_CG_with_volumes_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=()) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value="") + def test_create_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members, + _mock_rg): + self.driver.create_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCNoFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage): + 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 _ 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 _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -2981,21 +3556,23 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): configuration.safe_get.return_value = 'FCFAST' configuration.config_group = 'FCFAST' - self.stubs.Set(EMCVMAXCommon, '_get_ecom_connection', + self.stubs.Set(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) instancename = FakeCIMInstanceName() - self.stubs.Set(EMCVMAXUtils, 'get_instance_name', + 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 = EMCVMAXFCDriver(configuration=configuration) + driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() driver.common.conn = FakeEcomConnection() driver.zonemanager_lookup_service = None self.driver = driver - self.driver.utils = EMCVMAXUtils(object) - self.driver.masking = EMCVMAXMasking('FC') + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) + self.driver.masking = emc_vmax_masking.EMCVMAXMasking('FC') def create_fake_config_file_fast(self): @@ -3066,24 +3643,27 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): def fake_sleep(self, seconds): return + def fake_is_v3(self, conn, serialNumber): + return False + @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'is_tiering_policy_enabled', return_value=True) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_tier_policy_by_name', return_value=None) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', return_value=(1234, 1200)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_array_name_from_file', return_value="123456789") def test_get_volume_stats_fast(self, @@ -3099,11 +3679,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_create_volume_fast_success( @@ -3116,11 +3696,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value={'storagetype: stripedmetacount': '4', 'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_create_volume_fast_striped_success( @@ -3132,7 +3712,23 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_fast.EMCVMAXFast, + 'get_pool_associated_to_policy', + return_value=1) + def test_create_volume_in_CG_fast_success( + self, _mock_volume_type, mock_storage_system, mock_pool_policy): + self.driver.create_volume(self.data.test_volume_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) def test_delete_volume_fast_success(self, _mock_volume_type, @@ -3164,7 +3760,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] keys['SystemName'] = notfound_delete_vol['SystemName'] keys['DeviceID'] = notfound_delete_vol['DeviceID'] - keys['SystemCreationClassName'] =\ + keys['SystemCreationClassName'] = \ notfound_delete_vol['SystemCreationClassName'] name['keybindings'] = keys notfound_delete_vol['volume_type_id'] = 'abc' @@ -3177,15 +3773,15 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) def test_delete_volume_fast_failed( @@ -3202,20 +3798,15 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, 'get_masking_view_from_storage_group', return_value=EMCVMAXCommonData.lunmaskctrl_name) @mock.patch.object( - EMCVMAXProvision, - '_find_new_storage_group', - return_value='Any') - @mock.patch.object( - EMCVMAXMasking, - '_check_adding_volume_to_storage_group', - return_value=None) - def test_map_fast_success(self, mock_add_check, mock_new_sg, - mock_maskingview, mock_volume_type): - self.data.test_volume['volume_name'] = "vmax-1234567" + emc_vmax_common.EMCVMAXCommon, + '_is_same_host', + return_value=True) + def test_map_fast_success(self, _mock_volume_type, mock_maskingview, + mock_is_same_host): common = self.driver.common common.get_target_wwns = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) @@ -3233,7 +3824,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, 'find_device_number', return_value={'Name': "0001"}) def test_map_fast_failed(self, mock_wrap_group, mock_maskingview): @@ -3248,15 +3839,23 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value={'volume_backend_name': 'FCFAST', 'FASTPOLICY': 'FC_GOLD1'}) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, 'get_masking_view_by_volume', return_value=EMCVMAXCommonData.lunmaskctrl_name) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='myInitGroup') + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + 'get_initiator_group_from_masking_view', + return_value='myInitGroup') + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, 'get_masking_views_by_port_group', return_value=[]) def test_detach_fast_success(self, mock_volume_type, mock_maskingview, - mock_mvs): + mock_ig, mock_igc, mock_mv): common = self.driver.common common.get_target_wwns = mock.Mock( return_value=EMCVMAXCommonData.target_wwns) @@ -3272,7 +3871,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_size', return_value='2147483648') def test_extend_volume_fast_success(self, _mock_volume_type, @@ -3285,10 +3884,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'check_if_volume_is_extendable', return_value='False') - def test_extend_volume_striped_fast_failed(self, _mock_volume_type, + def test_extend_volume_striped_fast_failed(self, + _mock_volume_type, _mock_is_extendable): newSize = '2' self.assertRaises(exception.VolumeBackendAPIException, @@ -3305,28 +3905,24 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_snapshot_different_sizes_meta_fast_success( - self, mock_volume_type, mock_volume, mock_meta, - mock_size, mock_pool, mock_policy, mock_wait): + self, mock_volume_type, mock_volume, + mock_meta, mock_size, mock_pool, mock_policy): self.data.test_volume['volume_name'] = "vmax-1234567" common = self.driver.common @@ -3351,34 +3947,71 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', + emc_vmax_utils.EMCVMAXUtils, + 'find_sync_sv_by_target', return_value=(None, None)) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', - return_value=None) + return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) - def test_create_clone_simple_volume_fast_success( - self, mock_volume_type, mock_volume, - mock_sync_sv, mock_meta, mock_wait): + emc_vmax_utils.EMCVMAXUtils, + 'get_meta_members_capacity_in_bit', + return_value=[1234567]) + def test_create_volume_from_same_size_meta_snapshot( + self, mock_volume_type, mock_sync_sv, mock_meta, mock_size): self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.common.utils.find_storage_configuration_service = ( - mock.Mock(return_value=EMCVMAXCommonData.storage_system)) - self.driver.common._get_or_create_default_storage_group = ( - mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) - self.driver.common.fast.is_volume_in_default_SG = ( + common = self.driver.common + common.fast.is_volume_in_default_SG = mock.Mock(return_value=True) + self.driver.create_volume_from_snapshot( + self.data.test_volume, self.data.test_volume) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST', + 'FASTPOLICY': 'FC_GOLD1'}) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_replication_service', + return_value=None) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_sync_sv_by_target', + return_value=(None, None)) + def test_create_volume_from_snapshot_fast_failed( + self, mock_volume_type, + mock_rep_service, mock_sync_sv): + self.data.test_volume['volume_name'] = "vmax-1234567" + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + self.data.test_volume, + EMCVMAXCommonData.test_source_volume) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + FakeDB, + 'volume_get', + return_value=EMCVMAXCommonData.test_source_volume) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_storage_sync_sv_sv', + return_value=(None, None)) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'get_volume_meta_head', + return_value=None) + def test_create_clone_simple_volume_fast_success( + self, mock_volume_type, mock_volume, mock_sync_sv, + mock_simple_volume): + self.data.test_volume['volume_name'] = "vmax-1234567" + self.driver.common.fast.is_volume_in_default_SG = ( mock.Mock(return_value=True)) - self.driver.create_cloned_volume( - self.data.test_volume, - EMCVMAXCommonData.test_source_volume) + self.driver.create_cloned_volume(self.data.test_volume, + EMCVMAXCommonData.test_source_volume) @mock.patch.object( volume_types, @@ -3389,28 +4022,24 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXFast, + emc_vmax_fast.EMCVMAXFast, 'get_pool_associated_to_policy', return_value=1) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_volume_meta_head', return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'get_meta_members_capacity_in_bit', return_value=[1234567, 7654321]) @mock.patch.object( - EMCVMAXCommon, + emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - EMCVMAXUtils, - 'wait_for_sync', - return_value=None) def test_create_clone_fast_failed( - self, mock_volume_type, mock_vol, - mock_policy, mock_meta, mock_size, mock_pool, mock_wait): + self, mock_volume_type, mock_vol, mock_policy, + mock_meta, mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.common._modify_and_get_composite_volume_instance = ( mock.Mock(return_value=(1L, None))) @@ -3432,11 +4061,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( - EMCVMAXUtils, + emc_vmax_utils.EMCVMAXUtils, 'parse_pool_instance_id', return_value=('silver', 'SYMMETRIX+000195900551')) @mock.patch.object( - EMCVMAXMasking, + emc_vmax_masking.EMCVMAXMasking, '_wrap_get_storage_group_from_volume', return_value=None) def test_retype_volume_fast_success( @@ -3445,34 +4074,604 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.data.test_ctxt, self.data.test_volume, self.data.new_type, self.data.diff, self.data.test_host) - # Bug 1382641 - def test_get_sg_associated_with_connector(self): - conn = self.fake_ecom_connection() - controllerConfigService = ( - self.driver.utils.find_controller_configuration_service( - conn, self.data.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_CG_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.create_consistencygroup( + self.data.test_ctxt, self.data.test_CG) - mockSgInstanceName1 = {} - mockSgInstanceName1['CreationClassName'] = 'CIM_DeviceMaskingGroup' - mockSgInstanceName1['ElementName'] = 'OS_1382641_SG' - mockSgInstanceName2 = {} - mockSgInstanceName2['CreationClassName'] = 'CIM_DeviceMaskingGroup' - mockSgInstanceName2['ElementName'] = 'OS_not_1382641_SG' - self.driver.masking.get_associated_masking_groups_from_device = ( - mock.Mock(return_value=[mockSgInstanceName1, mockSgInstanceName2])) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + def test_delete_CG_no_volumes_fast_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) - volumeInstanceName = ( - conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) - volumeName = "1382641-Vol" - sgInstanceName = self.driver.masking._get_sg_associated_with_connector( - conn, controllerConfigService, volumeInstanceName, - volumeName, self.data.connector) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_CG_with_volumes_fast_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=()) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value="") + def test_create_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members, + _mock_rg): + self.driver.create_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'FCFAST'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_snapshot_for_CG_no_fast_success( + self, _mock_volume_type, _mock_storage): + self.driver.delete_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) + + +class EMCV3DriverTestCase(test.TestCase): + + def setUp(self): + + self.data = EMCVMAXCommonData() + + self.data.storage_system = 'SYMMETRIX-+-000197200056' + + self.tempdir = tempfile.mkdtemp() + super(EMCV3DriverTestCase, self).setUp() + self.config_file_path = None + self.create_fake_config_file_fast() + self.addCleanup(self._cleanup) + + configuration = mock.Mock() + configuration.cinder_emc_config_file = self.config_file_path + configuration.safe_get.return_value = 'V3' + configuration.config_group = '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 + + def create_fake_config_file_fast(self): + + doc = Document() + emc = doc.createElement("EMC") + doc.appendChild(emc) + + ecomserverip = doc.createElement("EcomServerIp") + ecomserveriptext = doc.createTextNode("1.1.1.1") + emc.appendChild(ecomserverip) + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + ecomserverporttext = doc.createTextNode("10") + emc.appendChild(ecomserverport) + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + ecomusernametext = doc.createTextNode("user") + emc.appendChild(ecomusername) + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + ecompasswordtext = doc.createTextNode("pass") + emc.appendChild(ecompassword) + ecompassword.appendChild(ecompasswordtext) + + portgroup = doc.createElement("PortGroup") + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + pool = doc.createElement("Pool") + pooltext = doc.createTextNode("SRP_1") + emc.appendChild(pool) + pool.appendChild(pooltext) + + array = doc.createElement("Array") + arraytext = doc.createTextNode("0123456789") + emc.appendChild(array) + array.appendChild(arraytext) + + slo = doc.createElement("SLO") + slotext = doc.createTextNode("Bronze") + emc.appendChild(slo) + slo.appendChild(slotext) + + workload = doc.createElement("Workload") + workloadtext = doc.createTextNode("DSS") + emc.appendChild(workload) + workload.appendChild(workloadtext) + + portgroups = doc.createElement("PortGroups") + portgroups.appendChild(portgroup) + emc.appendChild(portgroups) + + timeout = doc.createElement("Timeout") + timeouttext = doc.createTextNode("0") + emc.appendChild(timeout) + timeout.appendChild(timeouttext) + + filename = 'cinder_emc_config_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 + + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_storageSystem', + return_value=None) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'parse_array_name_from_file', + return_value="123456789") + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'isArrayV3', + return_value=True) + def test_get_volume_stats_v3( + self, mock_storage_system, mock_array, mock_is_v3): + self.driver.get_volume_stats(True) - (self.driver.masking.get_associated_masking_groups_from_device. - assert_called_once_with(conn, volumeInstanceName)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_get_supported_size_range_for_SLO', + return_value={'MaximumVolumeSize': '30000000000', + 'MinimumVolumeSize': '100000'}) + def test_create_volume_v3_success( + self, _mock_volume_type, mock_storage_system, mock_range): + self.driver.create_volume(self.data.test_volume_v3) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @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_utils.EMCVMAXUtils, + 'parse_slo_from_file', + return_value='NONE') + 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) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @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_utils.EMCVMAXUtils, + 'parse_slo_from_file', + return_value='Bogus') + def test_create_volume_v3_invalid_slo_failed( + self, _mock_volume_type, mock_storage_system, + mock_range, mock_slo): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume, + self.data.test_volume) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_get_supported_size_range_for_SLO', + return_value={'MaximumVolumeSize': '30000000000', + 'MinimumVolumeSize': '100000'}) + 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) + + @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) + + @mock.patch.object( + FakeDB, + 'volume_get', + return_value=EMCVMAXCommonData.test_source_volume) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_get_supported_size_range_for_SLO', + return_value={'MaximumVolumeSize': '30000000000', + 'MinimumVolumeSize': '100000'}) + 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) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + FakeDB, + 'volume_get', + return_value=EMCVMAXCommonData.test_source_volume) + 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) + + @mock.patch.object( + FakeDB, + 'volume_get', + return_value=EMCVMAXCommonData.test_source_volume) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_get_supported_size_range_for_SLO', + return_value={'MaximumVolumeSize': '30000000000', + 'MinimumVolumeSize': '100000'}) + 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" + + cloneVol = {} + cloneVol['name'] = 'vol1' + cloneVol['id'] = '10' + cloneVol['CreationClassName'] = 'Symmm_StorageVolume' + cloneVol['SystemName'] = self.data.storage_system + cloneVol['DeviceID'] = cloneVol['id'] + cloneVol['SystemCreationClassName'] = 'Symm_StorageSystem' + cloneVol['volume_type_id'] = 'abc' + cloneVol['provider_location'] = None + cloneVol['NumberOfBlocks'] = 100 + cloneVol['BlockSize'] = 512 + name = {} + name['classname'] = 'Symm_StorageVolume' + keys = {} + keys['CreationClassName'] = cloneVol['CreationClassName'] + keys['SystemName'] = cloneVol['SystemName'] + keys['DeviceID'] = cloneVol['DeviceID'] + keys['NumberOfBlocks'] = cloneVol['NumberOfBlocks'] + keys['BlockSize'] = cloneVol['BlockSize'] + keys['SystemCreationClassName'] = \ + cloneVol['SystemCreationClassName'] + name['keybindings'] = keys + + self.driver.create_cloned_volume(cloneVol, self.data.test_volume) - self.assertEqual(mockSgInstanceName1['ElementName'], - sgInstanceName['ElementName']) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + 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) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + FakeDB, + 'volume_get_all_by_group', + return_value=None) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=None) + def test_delete_CG_no_volumes_v3_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( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_CG_with_volumes_v3_success( + self, _mock_volume_type, _mock_storage_system): + self.driver.delete_consistencygroup( + self.data.test_ctxt, self.data.test_CG) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + def test_migrate_volume_v3_success(self, _mock_volume_type): + self.driver.migrate_volume(self.data.test_ctxt, self.data.test_volume, + self.data.test_host) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + '_get_fast_settings_from_storage_group', + return_value='Gold+DSS_REP') + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'wrap_get_storage_group_from_volume', + return_value=None) + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + '_find_new_storage_group', + return_value='Any') + def test_retype_volume_v3_success( + self, _mock_volume_type, mock_fast_settings, + mock_storage_group, mock_found_SG): + self.assertTrue(self.driver.retype( + self.data.test_ctxt, self.data.test_volume, self.data.new_type, + self.data.diff, self.data.test_host_v3)) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + '_get_fast_settings_from_storage_group', + return_value='Bronze+DSS') + def test_retype_volume_same_host_failure( + self, _mock_volume_type, mock_fast_settings): + self.assertFalse(self.driver.retype( + self.data.test_ctxt, self.data.test_volume, self.data.new_type, + self.data.diff, self.data.test_host_v3)) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_members_of_replication_group', + return_value=()) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value=1) + def test_create_cgsnapshot_v3_success( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_members, + mock_rg): + provisionv3 = self.driver.common.provisionv3 + provisionv3.create_group_replica = mock.Mock(return_value=(0L, None)) + self.driver.create_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + repServ = self.conn.EnumerateInstanceNames("EMC_ReplicationService")[0] + provisionv3.create_group_replica.assert_called_once_with( + self.conn, repServ, + (None, EMCVMAXCommonData.test_CG), + (None, EMCVMAXCommonData.test_CG), '12de') + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_delete_cgsnapshot_v3_success( + self, _mock_volume_type, _mock_storage): + self.driver.delete_cgsnapshot( + self.data.test_ctxt, self.data.test_CG_snapshot) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + 'get_masking_view_from_storage_group', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_is_same_host', + return_value=True) + def test_map_v3_success( + self, _mock_volume_type, mock_maskingview, mock_is_same_host): + common = self.driver.common + common.get_target_wwns = mock.Mock( + return_value=EMCVMAXCommonData.target_wwns) + data = self.driver.initialize_connection( + self.data.test_volume, self.data.connector) + # Test the no lookup service, pre-zoned case. + common.get_target_wwns.assert_called_once_with( + EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) + for init, target in data['data']['initiator_target_map'].items(): + self.assertIn(init[::-1], target) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + 'find_device_number', + return_value={'Name': "0001"}) + def test_map_v3_failed(self, mock_wrap_group, mock_maskingview): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + self.data.test_volume, + self.data.connector) + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + 'get_masking_view_from_storage_group', + return_value=EMCVMAXCommonData.lunmaskctrl_name) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + '_find_initiator_masking_group', + return_value='myInitGroup') + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + 'get_initiator_group_from_masking_view', + return_value='myInitGroup') + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + 'get_masking_views_by_port_group', + return_value=[]) + def test_detach_v3_success(self, mock_volume_type, mock_maskingview, + mock_ig, mock_igc, mock_mv): + 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.data.connector) + common.get_target_wwns.assert_called_once_with( + EMCVMAXCommonData.storage_system, EMCVMAXCommonData.connector) + numTargetWwns = len(EMCVMAXCommonData.target_wwns) + self.assertEqual(numTargetWwns, len(data['data'])) def _cleanup(self): bExists = os.path.exists(self.config_file_path) diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index c0a46461a..d1c3e32ec 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import inspect import os.path from oslo_config import cfg @@ -22,8 +23,10 @@ from cinder import exception from cinder.i18n import _, _LE, _LI, _LW from cinder.openstack.common import log as logging from cinder.volume.drivers.emc import emc_vmax_fast +from cinder.volume.drivers.emc import emc_vmax_https 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 @@ -50,6 +53,10 @@ STRIPECOUNT = 'storagetype:stripecount' MEMBERCOUNT = 'storagetype:membercount' STRIPED = 'striped' CONCATENATED = 'concatenated' +# V3 +SLO = 'storagetype:slo' +WORKLOAD = 'storagetype:workload' +ISV3 = 'isV3' emc_opts = [ cfg.StrOpt('cinder_emc_config_file', @@ -67,6 +74,7 @@ class EMCVMAXCommon(object): It supports VNX and VMAX arrays. """ + VERSION = "2.0.0" stats = {'driver_version': '1.0', 'free_capacity_gb': 0, @@ -76,12 +84,19 @@ 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} + def __init__(self, prtcl, configuration=None): if not pywbemAvailable: LOG.info(_LI( - 'Module PyWBEM not installed. ' - 'Install PyWBEM using the python-pywbem package.')) + "Module PyWBEM not installed. " + "Install PyWBEM using the python-pywbem package.")) self.protocol = prtcl self.configuration = configuration @@ -94,6 +109,53 @@ class EMCVMAXCommon(object): self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl) self.fast = emc_vmax_fast.EMCVMAXFast(prtcl) self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl) + self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl) + self._gather_info() + + def _gather_info(self): + """Gather the relevant information for update_volume_stats. + """ + if hasattr(self.configuration, 'cinder_emc_config_file'): + self.pool_info['config_file'] = ( + self.configuration.cinder_emc_config_file) + else: + self.pool_info['config_file'] = ( + self.configuration.safe_get('cinder_emc_config_file')) + + self.pool_info['backend_name'] = ( + self.configuration.safe_get('volume_backend_name')) + LOG.debug( + "Updating volume stats on file %(emcConfigFileName)s on " + "backend %(backendName)s.", + {'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['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. @@ -104,8 +166,8 @@ class EMCVMAXCommon(object): For a striped compositeType: The user must supply an extra spec to determine how many metas - will make up the striped volume. If the meta size is greater than - 240GB an error is returned to the user. Otherwise the + will make up the striped volume. If the meta size is greater + than 240GB an error is returned to the user. Otherwise the EMCNumberOfMembers is what the user specifies. :param volume: volume Object @@ -114,19 +176,42 @@ class EMCVMAXCommon(object): """ volumeSize = int(self.utils.convert_gb_to_bits(volume['size'])) volumeName = volume['id'] - extraSpecs = self._initial_setup(volume) self.conn = self._get_ecom_connection() - rc, volumeDict, storageSystemName = self._create_composite_volume( - volume, extraSpecs, volumeName, volumeSize) + if extraSpecs[ISV3]: + rc, volumeDict, storageSystemName = ( + self._create_v3_volume(volume, extraSpecs, + volumeName, volumeSize)) + else: + rc, volumeDict, storageSystemName = ( + self._create_composite_volume(volume, extraSpecs, + volumeName, volumeSize)) + + # If volume is created as part of a consistency group. + if 'consistencygroup_id' in volume and volume['consistencygroup_id']: + cgName = self.utils.truncate_string( + volume['consistencygroup_id'], 8) + volumeInstance = self.utils.find_volume_instance( + self.conn, volumeDict, volumeName) + replicationService = ( + self.utils.find_replication_service(self.conn, + storageSystemName)) + cgInstanceName = ( + self._find_consistency_group(replicationService, cgName)) + self.provision.add_volume_to_cg(self.conn, + replicationService, + cgInstanceName, + volumeInstance.path, + cgName, + volumeName) LOG.info(_LI("Leaving create_volume: %(volumeName)s " "Return code: %(rc)lu " - "volume dict: %(name)s") - % {'volumeName': volumeName, - 'rc': rc, - 'name': volumeDict}) + "volume dict: %(name)s."), + {'volumeName': volumeName, + 'rc': rc, + 'name': volumeDict}) return volumeDict @@ -139,6 +224,27 @@ class EMCVMAXCommon(object): :param snapshot - snapshot object :returns: cloneVolumeDict - the cloned volume dictionary """ + LOG.debug("Entering create_volume_from_snapshot.") + self._initial_setup(volume) + self.conn = self._get_ecom_connection() + snapshotInstance = self._find_lun(snapshot) + storageSystem = snapshotInstance['SystemName'] + + syncName = self.utils.find_sync_sv_by_target( + self.conn, storageSystem, snapshotInstance, True) + if syncName is not None: + repservice = self.utils.find_replication_service(self.conn, + storageSystem) + if repservice is None: + exception_message = (_("Cannot find Replication Service to " + "create volume for snapshot %s.") + % snapshotInstance) + raise exception.VolumeBackendAPIException( + data=exception_message) + + self.provision.delete_clone_relationship( + self.conn, repservice, syncName) + return self._create_cloned_volume(volume, snapshot) def create_cloned_volume(self, cloneVolume, sourceVolume): @@ -151,23 +257,23 @@ class EMCVMAXCommon(object): return self._create_cloned_volume(cloneVolume, sourceVolume) def delete_volume(self, volume): - """Deletes a EMC(VMAX) volume + """Deletes a EMC(VMAX) volume. :param volume: volume Object """ - LOG.info(_LI("Deleting Volume: %(volume)s") - % {'volume': volume['name']}) + LOG.info(_LI("Deleting Volume: %(volume)s"), + {'volume': volume['name']}) rc, volumeName = self._delete_volume(volume) LOG.info(_LI("Leaving delete_volume: %(volumename)s Return code: " - "%(rc)lu") - % {'volumename': volumeName, - 'rc': rc}) + "%(rc)lu."), + {'volumename': volumeName, + 'rc': rc}) def create_snapshot(self, snapshot, volume): """Creates a snapshot. - For VMAX, replace snapshot with clone + For VMAX, replace snapshot with clone. :param snapshot: snapshot object :param volume: volume Object to create snapshot from @@ -181,37 +287,34 @@ class EMCVMAXCommon(object): :param snapshot: snapshot object :param volume: volume Object to create snapshot from """ - LOG.info(_LI("Delete Snapshot: %(snapshotName)s ") - % {'snapshotName': snapshot['name']}) - rc, snapshotName = self._delete_volume(snapshot) - LOG.debug("Leaving delete_snapshot: %(snapshotname)s Return code: " - "%(rc)lu " - % {'snapshotname': snapshotName, - 'rc': rc}) + LOG.info(_LI("Delete Snapshot: %(snapshotName)s."), + {'snapshotName': snapshot['name']}) + self._delete_snapshot(snapshot) - def _remove_members(self, controllerConfigService, volumeInstance, - extraSpecs, connector): + def _remove_members(self, controllerConfigService, + volumeInstance, extraSpecs, connector): """This method unmaps a volume from a host. Removes volume from the Device Masking Group that belongs to a Masking View. Check if fast policy is in the extra specs. If it isn't we do - not need to do anything for FAST. + not need to do any thing for FAST. Assume that isTieringPolicySupported is False unless the FAST - policy is in the extra specs and tiering is enabled on the array + policy is in the extra specs and tiering is enabled on the array. :param controllerConfigService: instance name of ControllerConfigurationService - :param volumeInstance: volume instanceObject + :param volume: volume Object :param extraSpecs: the volume extra specs :param connector: the connector object """ volumeName = volumeInstance['ElementName'] - LOG.debug("Detaching volume %s" % volumeName) - fastPolicyName = extraSpecs[FASTPOLICY] + LOG.debug("Detaching volume %s.", volumeName) + fastPolicyName = extraSpecs.get(FASTPOLICY, None) + isV3 = extraSpecs[ISV3] return self.masking.remove_and_reset_members( self.conn, controllerConfigService, volumeInstance, - fastPolicyName, volumeName, connector) + fastPolicyName, volumeName, isV3, connector) def _unmap_lun(self, volume, connector): """Unmaps a volume from the host. @@ -222,14 +325,14 @@ class EMCVMAXCommon(object): """ extraSpecs = self._initial_setup(volume) volumename = volume['name'] - LOG.info(_LI("Unmap volume: %(volume)s") - % {'volume': volumename}) + LOG.info(_LI("Unmap volume: %(volume)s."), + {'volume': volumename}) device_info = self.find_device_number(volume) device_number = device_info['hostlunid'] if device_number is None: - LOG.info(_LI("Volume %s is not mapped. No volume to unmap.") - % (volumename)) + LOG.info(_LI("Volume %s is not mapped. No volume to unmap."), + (volumename)) return vol_instance = self._find_lun(volume) @@ -240,7 +343,7 @@ class EMCVMAXCommon(object): if configservice is None: exception_message = (_("Cannot find Controller Configuration " "Service for storage system " - "%(storage_system)s") + "%(storage_system)s.") % {'storage_system': storage_system}) raise exception.VolumeBackendAPIException(data=exception_message) @@ -252,7 +355,7 @@ class EMCVMAXCommon(object): The volume may be already mapped, if this is so the deviceInfo tuple is returned. If the volume is not already mapped then we need to - gather information to either 1. Create a new masking view or 2. Add + gather information to either 1. Create an new masking view or 2. Add the volume to an existing storage group within an already existing maskingview. @@ -276,23 +379,26 @@ class EMCVMAXCommon(object): extraSpecs = self._initial_setup(volume) volumeName = volume['name'] - LOG.info(_LI("Initialize connection: %(volume)s") - % {'volume': volumeName}) + LOG.info(_LI("Initialize connection: %(volume)s."), + {'volume': volumeName}) self.conn = self._get_ecom_connection() deviceInfoDict = self.find_device_number(volume) + if ('hostlunid' in deviceInfoDict and deviceInfoDict['hostlunid'] is not None): isSameHost = self._is_same_host(connector, deviceInfoDict) if isSameHost: - # Device is already mapped so we will leave the state as is. + # Device is already mapped to same host so we will leave + # the state as is. + deviceNumber = deviceInfoDict['hostlunid'] LOG.info(_LI("Volume %(volume)s is already mapped. " - "The device number is %(deviceNumber)s.") - % {'volume': volumeName, - 'deviceNumber': deviceNumber}) + "The device number is %(deviceNumber)s."), + {'volume': volumeName, + 'deviceNumber': deviceNumber}) else: - deviceInfoDict = self._attach_volume(volume, connector, - extraSpecs, True) + deviceInfoDict = self._attach_volume( + volume, connector, extraSpecs, True) else: deviceInfoDict = self._attach_volume(volume, connector, extraSpecs) @@ -327,11 +433,12 @@ class EMCVMAXCommon(object): if 'hostlunid' not in deviceInfoDict: # Did not successfully attach to host, # so a rollback for FAST is required. - LOG.error(_LE("Error Attaching volume %(vol)s.") - % {'vol': volumeName}) - if rollbackDict['fastPolicyName'] is not None: + LOG.error(_LE("Error Attaching volume %(vol)s."), + {'vol': volumeName}) + if ((rollbackDict['fastPolicyName'] is not None) or + (rollbackDict['isV3'] is not None)): (self.masking - .check_if_rollback_action_for_masking_required( + ._check_if_rollback_action_for_masking_required( self.conn, rollbackDict)) exception_message = ("Error Attaching volume %(vol)s." % {'vol': volumeName}) @@ -368,8 +475,8 @@ class EMCVMAXCommon(object): self._initial_setup(volume) volumename = volume['name'] - LOG.info(_LI("Terminate connection: %(volume)s") - % {'volume': volumename}) + LOG.info(_LI("Terminate connection: %(volume)s."), + {'volume': volumename}) self.conn = self._get_ecom_connection() self._unmap_lun(volume, connector) @@ -412,62 +519,9 @@ class EMCVMAXCommon(object): additionalVolumeSize = self.utils.convert_gb_to_bits( additionalVolumeSize) - # Is the volume extendable. - isConcatenated = self.utils.check_if_volume_is_extendable( - self.conn, volumeInstance) - if 'True' not in isConcatenated: - exceptionMessage = (_( - "Volume: %(volumeName)s is not a concatenated volume. " - "You can only perform extend on concatenated volume. " - "Exiting...") - % {'volumeName': volumeName}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException(data=exceptionMessage) - else: - compositeType = self.utils.get_composite_type(CONCATENATED) - - LOG.debug("Extend Volume: %(volume)s New size: %(newSize)s GBs" - % {'volume': volumeName, - 'newSize': newSize}) - - deviceId = volumeInstance['DeviceID'] - storageSystemName = volumeInstance['SystemName'] - LOG.debug( - "Device ID: %(deviceid)s: Storage System: " - "%(storagesystem)s" - % {'deviceid': deviceId, - 'storagesystem': storageSystemName}) - - storageConfigService = self.utils.find_storage_configuration_service( - self.conn, storageSystemName) - - elementCompositionService = ( - self.utils.find_element_composition_service( - self.conn, storageSystemName)) - - # create a volume to the size of the - # newSize - oldSize = additionalVolumeSize. - unboundVolumeInstance = self._create_and_get_unbound_volume( - self.conn, storageConfigService, volumeInstance.path, - additionalVolumeSize) - if unboundVolumeInstance is None: - exceptionMessage = (_( - "Error Creating unbound volume on an Extend operation")) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException(data=exceptionMessage) - - # Add the new unbound volume to the original composite volume. - rc, modifiedVolumeDict = ( - self._modify_and_get_composite_volume_instance( - self.conn, elementCompositionService, volumeInstance, - unboundVolumeInstance.path, volumeName, compositeType)) - if modifiedVolumeDict is None: - exceptionMessage = (_( - "On an Extend Operation, error adding volume to composite " - "volume: %(volumename)s. ") - % {'volumename': volumeName}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException(data=exceptionMessage) + # This is V2 + rc, modifiedVolumeDict = self._extend_composite_volume( + volumeInstance, volumeName, newSize, additionalVolumeSize) # Check the occupied space of the new extended volume. extendedVolumeInstance = self.utils.find_volume_instance( @@ -476,9 +530,9 @@ class EMCVMAXCommon(object): self.conn, extendedVolumeInstance) LOG.debug( "The actual volume size of the extended volume: %(volumeName)s " - "is %(volumeSize)s" - % {'volumeName': volumeName, - 'volumeSize': extendedVolumeSize}) + "is %(volumeSize)s.", + {'volumeName': volumeName, + 'volumeSize': extendedVolumeSize}) # If the requested size and the actual size don't # tally throw an exception. @@ -488,129 +542,83 @@ class EMCVMAXCommon(object): if diffVolumeSize != 0: exceptionMessage = (_( "The requested size : %(requestedSize)s is not the same as " - "resulting size: %(resultSize)s") + "resulting size: %(resultSize)s.") % {'requestedSize': newSizeBits, 'resultSize': extendedVolumeSize}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) LOG.debug( - "Leaving extend_volume: %(volumeName)s " - "Return code: %(rc)lu " - "volume dict: %(name)s" - % {'volumeName': volumeName, - 'rc': rc, - 'name': modifiedVolumeDict}) + "Leaving extend_volume: %(volumeName)s. " + "Return code: %(rc)lu, " + "volume dict: %(name)s.", + {'volumeName': volumeName, + 'rc': rc, + 'name': modifiedVolumeDict}) return modifiedVolumeDict def update_volume_stats(self): """Retrieve stats info. """ - if hasattr(self.configuration, 'cinder_emc_config_file'): - emcConfigFileName = self.configuration.cinder_emc_config_file - else: - emcConfigFileName = self.configuration.safe_get( - 'cinder_emc_config_file') - backendName = self.configuration.safe_get('volume_backend_name') - LOG.debug( - "Updating volume stats on file %(emcConfigFileName)s on " - "backend %(backendName)s " - % {'emcConfigFileName': emcConfigFileName, - 'backendName': backendName}) - - poolName = self.utils.parse_pool_name_from_file(emcConfigFileName) - if poolName is None: - LOG.error(_LE( - "PoolName %(poolName)s must be in the file " - "%(emcConfigFileName)s ") - % {'poolName': poolName, - 'emcConfigFileName': emcConfigFileName}) - arrayName = self.utils.parse_array_name_from_file(emcConfigFileName) - if arrayName is None: - LOG.error(_LE( - "Array Serial Number %(arrayName)s must be in the file " - "%(emcConfigFileName)s ") - % {'arrayName': arrayName, - 'emcConfigFileName': emcConfigFileName}) - # This value can be None. - fastPolicyName = self.utils.parse_fast_policy_name_from_file( - emcConfigFileName) - if fastPolicyName is not None: - LOG.debug( - "Fast policy %(fastPolicyName)s is enabled on %(arrayName)s. " - % {'fastPolicyName': fastPolicyName, - 'arrayName': arrayName}) + 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: - LOG.debug( - "No Fast policy for Array:%(arrayName)s " - "backend:%(backendName)s" - % {'arrayName': arrayName, - 'backendName': backendName}) - - if self.conn is None: - self._set_ecom_credentials(emcConfigFileName) - - storageSystemInstanceName = ( - self.utils.find_storageSystem(self.conn, arrayName)) - - isTieringPolicySupported = ( - self.fast.is_tiering_policy_enabled_on_storage_system( - self.conn, storageSystemInstanceName)) - - if (fastPolicyName 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)) - 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, - '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)) - LOG.info(_LI( - "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, - '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}) + # 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, - 'volume_backend_name': backendName or self.__class__.__name__, + 'volume_backend_name': self.pool_info['backend_name'] + or self.__class__.__name__, 'vendor_name': "EMC", - 'driver_version': '2.0', + 'driver_version': self.VERSION, 'storage_protocol': 'unknown', - 'location_info': location_info} + 'location_info': location_info, + 'consistencygroup_support': True} + + return data + + def _update_srp_stats(self, emcConfigFileName, arrayName, poolName): + """Update SRP stats. + + :param emcConfigFileName: the EMC configuration file + :param arrayName: the array + :param poolName: the pool + :returns: location_info, total_capacity_gb, free_capacity_gb + """ - self.stats = data + totalManagedSpaceGbs, remainingManagedSpaceGbs = ( + self.utils.get_srp_pool_stats(self.conn, arrayName, poolName)) + + 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, + '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}) - return self.stats + return location_info, totalManagedSpaceGbs, remainingManagedSpaceGbs def retype(self, ctxt, volume, new_type, diff, host): """Migrate volume to another host using retype. @@ -626,8 +634,8 @@ class EMCVMAXCommon(object): volumeName = volume['name'] volumeStatus = volume['status'] - LOG.info(_LI("Migrating using retype Volume: %(volume)s") - % {'volume': volumeName}) + LOG.info(_LI("Migrating using retype Volume: %(volume)s."), + {'volume': volumeName}) extraSpecs = self._initial_setup(volume) self.conn = self._get_ecom_connection() @@ -635,33 +643,18 @@ class EMCVMAXCommon(object): volumeInstance = self._find_lun(volume) if volumeInstance is None: LOG.error(_LE("Volume %(name)s not found on the array. " - "No volume to migrate using retype.") - % {'name': volumeName}) - return False - - storageSystemName = volumeInstance['SystemName'] - isValid, targetPoolName, targetFastPolicyName = ( - self._is_valid_for_storage_assisted_migration( - volumeInstance.path, host, storageSystemName, - volumeName, volumeStatus)) - - if not isValid: - LOG.error(_LE("Volume %(name)s is not suitable for storage " - "assisted migration using retype") - % {'name': volumeName}) + "No volume to migrate using retype."), + {'name': volumeName}) return False - if volume['host'] != host['host']: - LOG.debug( - "Retype Volume %(name)s from source host %(sourceHost)s " - "to target host %(targetHost)s" - % {'name': volumeName, - 'sourceHost': volume['host'], - 'targetHost': host['host']}) - return self._migrate_volume( - volume, volumeInstance, targetPoolName, targetFastPolicyName, - extraSpecs[FASTPOLICY], new_type) - return True + if extraSpecs[ISV3]: + return self._slo_workload_migration(volumeInstance, volume, host, + volumeName, volumeStatus, + extraSpecs, new_type) + else: + return self._pool_migration(volumeInstance, volume, host, + volumeName, volumeStatus, + extraSpecs[FASTPOLICY], new_type) def migrate_volume(self, ctxt, volume, host, new_type=None): """Migrate volume to another host. @@ -674,7 +667,7 @@ class EMCVMAXCommon(object): :returns: boolean True/False :returns: list """ - LOG.warn(_LW("The VMAX plugin only supports Retype. " + LOG.warn(_LW("The VMAX plugin only supports Retype. " "If a pool based migration is necessary " "this will happen on a Retype " "From the command line: " @@ -708,19 +701,20 @@ class EMCVMAXCommon(object): if moved is False and sourceFastPolicyName is not None: # Return the volume to the default source fast policy storage # group because the migrate was unsuccessful. - LOG.warn(_LW("Failed to migrate: %(volumeName)s from " - "default source storage group " - "for FAST policy: %(sourceFastPolicyName)s " - "Attempting cleanup... ") - % {'volumeName': volumeName, - 'sourceFastPolicyName': sourceFastPolicyName}) + LOG.warn(_LW( + "Failed to migrate: %(volumeName)s from " + "default source storage group " + "for FAST policy: %(sourceFastPolicyName)s. " + "Attempting cleanup... "), + {'volumeName': volumeName, + 'sourceFastPolicyName': sourceFastPolicyName}) if sourcePoolInstanceName == self.utils.get_assoc_pool_from_volume( self.conn, volumeInstance.path): self._migrate_cleanup(self.conn, volumeInstance, storageSystemName, sourceFastPolicyName, volumeName) else: - # migrate was successful but still issues. + # Migrate was successful but still issues. self._migrate_rollback( self.conn, volumeInstance, storageSystemName, sourceFastPolicyName, volumeName, sourcePoolInstanceName) @@ -734,10 +728,11 @@ class EMCVMAXCommon(object): if not self._migrate_volume_fast_target( volumeInstance, storageSystemName, targetFastPolicyName, volumeName): - LOG.warn(_LW("Attempting a rollback of: %(volumeName)s to " - "original pool %(sourcePoolInstanceName)s ") - % {'volumeName': volumeName, - 'sourcePoolInstanceName': sourcePoolInstanceName}) + LOG.warn(_LW( + "Attempting a rollback of: %(volumeName)s to " + "original pool %(sourcePoolInstanceName)s."), + {'volumeName': volumeName, + 'sourcePoolInstanceName': sourcePoolInstanceName}) self._migrate_rollback( self.conn, volumeInstance, storageSystemName, sourceFastPolicyName, volumeName, sourcePoolInstanceName) @@ -753,7 +748,7 @@ class EMCVMAXCommon(object): """Full rollback. Failed on final step on adding migrated volume to new target - default storage group for the target FAST policy + default storage group for the target FAST policy. :param conn: connection info to ECOM :param volumeInstance: the volume instance @@ -765,8 +760,8 @@ class EMCVMAXCommon(object): :returns: int, the return code from migrate operation """ - LOG.warn(_LW("_migrate_rollback on : %(volumeName)s from ") - % {'volumeName': volumeName}) + LOG.warn(_LW("_migrate_rollback on : %(volumeName)s."), + {'volumeName': volumeName}) storageRelocationService = self.utils.find_storage_relocation_service( conn, storageSystemName) @@ -776,12 +771,11 @@ class EMCVMAXCommon(object): conn, storageRelocationService, volumeInstance.path, sourcePoolInstanceName) except Exception: - exceptionMessage = (_( + LOG.error(_LE( "Failed to return volume %(volumeName)s to " "original storage pool. Please contact your system " - "administrator to return it to the correct location ") - % {'volumeName': volumeName}) - LOG.error(exceptionMessage) + "administrator to return it to the correct location."), + {'volumeName': volumeName}) if sourceFastPolicyName is not None: self.add_to_default_SG( @@ -803,8 +797,8 @@ class EMCVMAXCommon(object): :returns: int, the return code from migrate operation """ - LOG.warn(_LW("_migrate_cleanup on : %(volumeName)s from ") - % {'volumeName': volumeName}) + LOG.warn(_LW("_migrate_cleanup on : %(volumeName)s."), + {'volumeName': volumeName}) controllerConfigurationService = ( self.utils.find_controller_configuration_service( @@ -843,7 +837,7 @@ class EMCVMAXCommon(object): """If the target host is FAST enabled. If the target host is FAST enabled then we need to add it to the - default storage group for that policy + default storage group for that policy. :param volumeInstance: the volume instance :param storageSystemName: the storage system name @@ -852,10 +846,11 @@ class EMCVMAXCommon(object): :returns: boolean True/False """ falseRet = False - LOG.info(_LI("Adding volume: %(volumeName)s to default storage group " - "for FAST policy: %(fastPolicyName)s ") - % {'volumeName': volumeName, - 'fastPolicyName': targetFastPolicyName}) + LOG.info(_LI( + "Adding volume: %(volumeName)s to default storage group " + "for FAST policy: %(fastPolicyName)s."), + {'volumeName': volumeName, + 'fastPolicyName': targetFastPolicyName}) controllerConfigurationService = ( self.utils.find_controller_configuration_service( @@ -866,11 +861,10 @@ class EMCVMAXCommon(object): self.conn, controllerConfigurationService, targetFastPolicyName, volumeInstance)) if defaultStorageGroupInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Unable to create or get default storage group for FAST policy" - ": %(fastPolicyName)s. ") - % {'fastPolicyName': targetFastPolicyName}) - LOG.error(exceptionMessage) + ": %(fastPolicyName)s."), + {'fastPolicyName': targetFastPolicyName}) return falseRet @@ -879,11 +873,10 @@ class EMCVMAXCommon(object): self.conn, controllerConfigurationService, volumeInstance, volumeName, targetFastPolicyName)) if defaultStorageGroupInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Failed to verify that volume was added to storage group for " - "FAST policy: %(fastPolicyName)s. ") - % {'fastPolicyName': targetFastPolicyName}) - LOG.error(exceptionMessage) + "FAST policy: %(fastPolicyName)s."), + {'fastPolicyName': targetFastPolicyName}) return falseRet return True @@ -903,8 +896,8 @@ class EMCVMAXCommon(object): volumeName = volume['name'] storageSystemName = volumeInstance['SystemName'] - LOG.debug("sourceFastPolicyName is : %(sourceFastPolicyName)s. " - % {'sourceFastPolicyName': sourceFastPolicyName}) + LOG.debug("sourceFastPolicyName is : %(sourceFastPolicyName)s.", + {'sourceFastPolicyName': sourceFastPolicyName}) # If the source volume is is FAST enabled it must first be removed # from the default storage group for that policy. @@ -920,11 +913,10 @@ class EMCVMAXCommon(object): targetPoolInstanceName = self.utils.get_pool_by_name( self.conn, targetPoolName, storageSystemName) if targetPoolInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Error finding targe pool instance name for pool: " - "%(targetPoolName)s. ") - % {'targetPoolName': targetPoolName}) - LOG.error(exceptionMessage) + "%(targetPoolName)s."), + {'targetPoolName': targetPoolName}) return falseRet try: rc = self.provision.migrate_volume_to_storage_pool( @@ -933,12 +925,12 @@ class EMCVMAXCommon(object): except Exception as e: # Rollback by deleting the volume if adding the volume to the # default storage group were to fail. - LOG.error(_LE("Exception: %s") % six.text_type(e)) - exceptionMessage = (_("Error migrating volume: %(volumename)s. " - "to target pool %(targetPoolName)s. ") - % {'volumename': volumeName, - 'targetPoolName': targetPoolName}) - LOG.error(exceptionMessage) + LOG.error(_LE("Exception: %s"), e) + LOG.error(_LE( + "Error migrating volume: %(volumename)s. " + "to target pool %(targetPoolName)s."), + {'volumename': volumeName, + 'targetPoolName': targetPoolName}) return falseRet # Check that the volume is now migrated to the correct storage pool, @@ -949,17 +941,16 @@ class EMCVMAXCommon(object): if (foundPoolInstanceName is None or (foundPoolInstanceName['InstanceID'] != targetPoolInstanceName['InstanceID'])): - exceptionMessage = (_( + LOG.error(_LE( "Volume : %(volumeName)s. was not successfully migrated to " - "target pool %(targetPoolName)s.") - % {'volumeName': volumeName, - 'targetPoolName': targetPoolName}) - LOG.error(exceptionMessage) + "target pool %(targetPoolName)s."), + {'volumeName': volumeName, + 'targetPoolName': targetPoolName}) return falseRet else: - LOG.debug("Terminating migration session on : %(volumeName)s. " - % {'volumeName': volumeName}) + LOG.debug("Terminating migration session on: %(volumeName)s.", + {'volumeName': volumeName}) self.provision._terminate_migrate_session( self.conn, volumeInstance.path) @@ -991,23 +982,24 @@ class EMCVMAXCommon(object): conn, controllerConfigurationService, volumeInstance.path, volumeName, sourceFastPolicyName)) except Exception as ex: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) - exceptionMessage = (_("Failed to remove: %(volumename)s. " - "from the default storage group for " - "FAST policy %(fastPolicyName)s. ") - % {'volumename': volumeName, - 'fastPolicyName': sourceFastPolicyName}) + LOG.error(_LE("Exception: %s"), ex) + exceptionMessage = (_( + "Failed to remove: %(volumename)s. " + "from the default storage group for " + "FAST policy %(fastPolicyName)s.") + % {'volumename': volumeName, + 'fastPolicyName': sourceFastPolicyName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) if defaultStorageGroupInstanceName is None: - warnMessage = (_("The volume: %(volumename)s. " - "was not first part of the default storage " - "group for FAST policy %(fastPolicyName)s.") - % {'volumename': volumeName, - 'fastPolicyName': sourceFastPolicyName}) - LOG.warn(warnMessage) + LOG.warn(_LW( + "The volume: %(volumename)s " + "was not first part of the default storage " + "group for FAST policy %(fastPolicyName)s."), + {'volumename': volumeName, + 'fastPolicyName': sourceFastPolicyName}) def add_to_default_SG( self, conn, volumeInstance, storageSystemName, @@ -1032,13 +1024,92 @@ class EMCVMAXCommon(object): conn, controllerConfigurationService, volumeInstance, volumeName, targetFastPolicyName)) if assocDefaultStorageGroupName is None: - errorMsg = (_LE( + LOG.error(_LE( "Failed to add %(volumeName)s " "to default storage group for fast policy " - "%(fastPolicyName)s ") - % {'volumeName': volumeName, - 'fastPolicyName': targetFastPolicyName}) - LOG.error(errorMsg) + "%(fastPolicyName)s."), + {'volumeName': volumeName, + 'fastPolicyName': targetFastPolicyName}) + + def _is_valid_for_storage_assisted_migration_v3( + self, volumeInstanceName, host, sourceArraySerialNumber, + sourcePoolName, volumeName, volumeStatus): + """Check if volume is suitable for storage assisted (pool) migration. + + :param volumeInstanceName: the volume instance id + :param host: the host object + :param sourceArraySerialNumber: the array serial number of + the original volume + :param sourcePoolName: the pool name + the original volume + :param volumeName: the name of the volume to be migrated + :param volumeStatus: the status of the volume e.g + :returns: boolean, True/False + :returns: string, targetSlo + :returns: string, targetWorkload + """ + falseRet = (False, None, None) + if 'location_info' not in host['capabilities']: + LOG.error(_LE('Error getting array, pool, SLO and workload.')) + return falseRet + info = host['capabilities']['location_info'] + + LOG.debug("Location info is : %(info)s.", + {'info': info}) + try: + infoDetail = info.split('#') + targetArraySerialNumber = infoDetail[0] + targetPoolName = infoDetail[1] + targetSlo = infoDetail[2] + targetWorkload = infoDetail[3] + except KeyError: + LOG.error(_LE("Error parsing array, pool, SLO and workload.")) + + if targetArraySerialNumber not in sourceArraySerialNumber: + LOG.error(_LE( + "The source array : %(sourceArraySerialNumber)s does not " + "match the target array: %(targetArraySerialNumber)s " + "skipping storage-assisted migration."), + {'sourceArraySerialNumber': sourceArraySerialNumber, + 'targetArraySerialNumber': targetArraySerialNumber}) + return falseRet + + if targetPoolName not in sourcePoolName: + LOG.error(_LE( + "Only SLO/workload migration within the same SRP Pool " + "is supported in this version " + "The source pool : %(sourcePoolName)s does not " + "match the target array: %(targetPoolName)s. " + "Skipping storage-assisted migration."), + {'sourcePoolName': sourcePoolName, + 'targetPoolName': targetPoolName}) + return falseRet + + foundStorageGroupInstanceName = ( + self.utils.get_storage_group_from_volume( + self.conn, volumeInstanceName)) + if foundStorageGroupInstanceName is None: + LOG.warn(_LW( + "Volume: %(volumeName)s is not currently " + "belonging to any storage group."), + {'volumeName': volumeName}) + + else: + storageGroupInstance = self.conn.GetInstance( + foundStorageGroupInstanceName) + emcFastSetting = self.utils._get_fast_settings_from_storage_group( + storageGroupInstance) + targetCombination = targetSlo + '+' + targetWorkload + if targetCombination in emcFastSetting: + LOG.error(_LE( + "No action required. Volume: %(volumeName)s is " + "already part of slo/workload combination: " + "%(targetCombination)s."), + {'volumeName': volumeName, + 'targetCombination': targetCombination}) + return falseRet + + return (True, targetSlo, targetWorkload) def _is_valid_for_storage_assisted_migration( self, volumeInstanceName, host, sourceArraySerialNumber, @@ -1057,29 +1128,28 @@ class EMCVMAXCommon(object): """ falseRet = (False, None, None) if 'location_info' not in host['capabilities']: - LOG.error(_LE('Error getting target pool name and array')) + LOG.error(_LE("Error getting target pool name and array.")) return falseRet info = host['capabilities']['location_info'] - LOG.debug("Location info is : %(info)s." - % {'info': info}) + LOG.debug("Location info is : %(info)s.", + {'info': info}) try: infoDetail = info.split('#') targetArraySerialNumber = infoDetail[0] targetPoolName = infoDetail[1] targetFastPolicy = infoDetail[2] - except Exception: - LOG.error(_LE("Error parsing target pool name, array, " - "and fast policy")) + except KeyError: + LOG.error(_LE( + "Error parsing target pool name, array, and fast policy.")) if targetArraySerialNumber not in sourceArraySerialNumber: - errorMessage = (_LE( + LOG.error(_LE( "The source array : %(sourceArraySerialNumber)s does not " - "match the target array: %(targetArraySerialNumber)s" - "skipping storage-assisted migration") - % {'sourceArraySerialNumber': sourceArraySerialNumber, - 'targetArraySerialNumber': targetArraySerialNumber}) - LOG.error(errorMessage) + "match the target array: %(targetArraySerialNumber)s, " + "skipping storage-assisted migration."), + {'sourceArraySerialNumber': sourceArraySerialNumber, + 'targetArraySerialNumber': targetArraySerialNumber}) return falseRet # Get the pool from the source array and check that is is different @@ -1089,25 +1159,24 @@ class EMCVMAXCommon(object): assocPoolInstance = self.conn.GetInstance( assocPoolInstanceName) if assocPoolInstance['ElementName'] == targetPoolName: - errorMessage = (_LE("No action required. Volume : %(volumeName)s " - "is already part of pool : %(pool)s") - % {'volumeName': volumeName, - 'pool': targetPoolName}) - LOG.error(errorMessage) + LOG.error(_LE( + "No action required. Volume: %(volumeName)s is " + "already part of pool: %(pool)s."), + {'volumeName': volumeName, + 'pool': targetPoolName}) return falseRet - LOG.info(_LI("Volume status is: %s"), volumeStatus) + LOG.info(_LI("Volume status is: %s."), volumeStatus) if (host['capabilities']['storage_protocol'] != self.protocol and (volumeStatus != 'available' and volumeStatus != 'retyping')): - errorMessage = (_LE( + LOG.error(_LE( "Only available volumes can be migrated between " - "different protocols")) - LOG.error(errorMessage) + "different protocols.")) return falseRet return (True, targetPoolName, targetFastPolicy) - def _set_config_file_and_get_extra_specs(self, volume, filename=None): + def _set_config_file_and_get_extra_specs(self, volume, volumeTypeId=None): """Given the volume object get the associated volumetype. Given the volume object get the associated volumetype and the @@ -1118,13 +1187,12 @@ class EMCVMAXCommon(object): :returns: tuple the extra specs tuple :returns: string configuration file """ - extraSpecs = self.utils.get_volumetype_extraspecs(volume) + extraSpecs = self.utils.get_volumetype_extraspecs(volume, volumeTypeId) configGroup = None # If there are no extra specs then the default case is assumed. if extraSpecs: configGroup = self.configuration.config_group - LOG.info(_LI("configGroup of current host: %s"), configGroup) configurationFile = self._register_config_file_from_config_group( configGroup) @@ -1136,40 +1204,72 @@ class EMCVMAXCommon(object): :returns: conn,the ecom connection """ - conn = pywbem.WBEMConnection(self.url, (self.user, self.passwd), - default_namespace='root/emc') + + if self.ecomUseSSL: + argspec = inspect.getargspec(pywbem.WBEMConnection.__init__) + if any("ca_certs" in s for s in argspec.args): + updatedPywbem = True + else: + updatedPywbem = False + pywbem.cim_http.wbem_request = emc_vmax_https.wbem_request + if updatedPywbem: + conn = pywbem.WBEMConnection( + self.url, + (self.user, self.passwd), + default_namespace='root/emc', + x509={"key_file": + self.configuration.safe_get( + 'driver_client_cert_key'), + "cert_file": + self.configuration.safe_get('driver_client_cert')}, + ca_certs=self.ecomCACert, + no_verification=self.ecomNoVerification) + else: + conn = pywbem.WBEMConnection( + self.url, + (self.user, self.passwd), + default_namespace='root/emc', + x509={"key_file": + self.configuration.safe_get( + 'driver_client_cert_key'), + "cert_file": + self.configuration.safe_get('driver_client_cert')}) + + else: + conn = pywbem.WBEMConnection( + self.url, + (self.user, self.passwd), + default_namespace='root/emc') + + conn.debug = True if conn is None: - exception_message = (_("Cannot connect to ECOM server")) + exception_message = (_("Cannot connect to ECOM server.")) raise exception.VolumeBackendAPIException(data=exception_message) return conn - def _find_pool_in_array(self, arrayStr, poolNameInStr): + def _find_pool_in_array(self, arrayStr, poolNameInStr, isV3): """Find a pool based on the pool name on a given array. :param arrayStr: the array Serial number (String) - :parampoolNameInStr: the name of the poolname (String) + :param poolNameInStr: the name of the poolname (String) + :param isv3: True/False :returns: foundPoolInstanceName, the CIM Instance Name of the Pool """ foundPoolInstanceName = None systemNameStr = None - storageSystemInstanceName = self.utils.find_storageSystem(self.conn, - arrayStr) + storageSystemInstanceName = self.utils.find_storageSystem( + self.conn, arrayStr) - vpools = self.conn.AssociatorNames( - storageSystemInstanceName, - resultClass='EMC_VirtualProvisioningPool') - - for vpool in vpools: - poolinstance = vpool['InstanceID'] - # Example: SYMMETRIX+000195900551+TP+Sol_Innov - poolnameStr, systemNameStr = self.utils.parse_pool_instance_id( - poolinstance) - if poolnameStr is not None and systemNameStr is not None: - if six.text_type(poolNameInStr) == six.text_type(poolnameStr): - foundPoolInstanceName = vpool - break + if isV3: + foundPoolInstanceName, systemNameStr = ( + self.utils.get_pool_and_system_name_v3( + self.conn, storageSystemInstanceName, poolNameInStr)) + else: + foundPoolInstanceName, systemNameStr = ( + self.utils.get_pool_and_system_name_v2( + self.conn, storageSystemInstanceName, poolNameInStr)) if foundPoolInstanceName is None: exceptionMessage = (_("Pool %(poolNameInStr)s is not found.") @@ -1184,9 +1284,9 @@ class EMCVMAXCommon(object): LOG.error(exception_message) raise exception.VolumeBackendAPIException(data=exception_message) - LOG.debug("Pool: %(pool)s SystemName: %(systemname)s." - % {'pool': foundPoolInstanceName, - 'systemname': systemNameStr}) + LOG.debug("Pool: %(pool)s SystemName: %(systemname)s.", + {'pool': foundPoolInstanceName, + 'systemname': systemNameStr}) return foundPoolInstanceName, systemNameStr def _find_lun(self, volume): @@ -1202,6 +1302,7 @@ class EMCVMAXCommon(object): loc = volume['provider_location'] if self.conn is None: self.conn = self._get_ecom_connection() + if isinstance(loc, six.string_types): name = eval(loc) @@ -1213,13 +1314,13 @@ class EMCVMAXCommon(object): self.conn, instancename) if foundVolumeinstance is None: - LOG.debug("Volume %(volumename)s not found on the array." - % {'volumename': volumename}) + LOG.debug("Volume %(volumename)s not found on the array.", + {'volumename': volumename}) else: LOG.debug("Volume name: %(volumename)s Volume instance: " - "%(foundVolumeinstance)s." - % {'volumename': volumename, - 'foundVolumeinstance': foundVolumeinstance}) + "%(foundVolumeinstance)s.", + {'volumename': volumename, + 'foundVolumeinstance': foundVolumeinstance}) return foundVolumeinstance @@ -1234,8 +1335,8 @@ class EMCVMAXCommon(object): """ snapshotname = snapshot['name'] volumename = volume['name'] - LOG.debug("Source: %(volumename)s Target: %(snapshotname)s." - % {'volumename': volumename, 'snapshotname': snapshotname}) + LOG.debug("Source: %(volumename)s Target: %(snapshotname)s.", + {'volumename': volumename, 'snapshotname': snapshotname}) snapshot_instance = self._find_lun(snapshot) volume_instance = self._find_lun(volume) @@ -1248,14 +1349,14 @@ class EMCVMAXCommon(object): if foundsyncname is None: LOG.debug( "Source: %(volumename)s Target: %(snapshotname)s. " - "Storage Synchronized not found. " - % {'volumename': volumename, - 'snapshotname': snapshotname}) + "Storage Synchronized not found.", + {'volumename': volumename, + 'snapshotname': snapshotname}) else: - LOG.debug("Storage system: %(storage_system)s " - "Storage Synchronized instance: %(sync)s." - % {'storage_system': storage_system, - 'sync': foundsyncname}) + LOG.debug("Storage system: %(storage_system)s. " + "Storage Synchronized instance: %(sync)s.", + {'storage_system': storage_system, + 'sync': foundsyncname}) # Wait for SE_StorageSynchronized_SV_SV to be fully synced. if waitforsync: self.utils.wait_for_sync(self.conn, foundsyncname) @@ -1279,15 +1380,16 @@ class EMCVMAXCommon(object): LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - LOG.debug("Found %(name)s: %(initiator)s." - % {'name': name, - 'initiator': foundinitiatornames}) + LOG.debug("Found %(name)s: %(initiator)s.", + {'name': name, + 'initiator': foundinitiatornames}) return foundinitiatornames def find_device_number(self, volume): """Given the volume dict find a device number. - Find a device number that a host can see for a volume. + Find a device number that a host can see + for a volume. :param volume: the volume dict :returns: data, the data dict @@ -1327,15 +1429,15 @@ class EMCVMAXCommon(object): if foundNumDeviceNumber is None: LOG.debug( "Device number not found for volume " - "%(volumeName)s %(volumeInstance)s." - % {'volumeName': volumeName, - 'volumeInstance': volumeInstance.path}) + "%(volumeName)s %(volumeInstance)s.", + {'volumeName': volumeName, + 'volumeInstance': volumeInstance.path}) data = {'hostlunid': foundNumDeviceNumber, 'storagesystem': storageSystemName, 'maskingview': foundMaskingViewName} - LOG.debug("Device info: %(data)s." % {'data': data}) + LOG.debug("Device info: %(data)s.", {'data': data}) return data @@ -1355,22 +1457,23 @@ class EMCVMAXCommon(object): connector, storageHardwareService) LOG.debug( - "EMCGetTargetEndpoints: Service: %(service)s " - "Storage HardwareIDs: %(hardwareIds)s." - % {'service': storageHardwareService, - 'hardwareIds': hardwareIdInstances}) + "EMCGetTargetEndpoints: Service: %(service)s, " + "Storage HardwareIDs: %(hardwareIds)s.", + {'service': storageHardwareService, + 'hardwareIds': hardwareIdInstances}) for hardwareIdInstance in hardwareIdInstances: - LOG.debug("HardwareID instance is : %(hardwareIdInstance)s " - % {'hardwareIdInstance': hardwareIdInstance}) + LOG.debug("HardwareID instance is: %(hardwareIdInstance)s.", + {'hardwareIdInstance': hardwareIdInstance}) try: - rc, targetEndpoints = self.provision.get_target_endpoints( - self.conn, storageHardwareService, hardwareIdInstance) + _, targetEndpoints = ( + self.provision.get_target_endpoints( + self.conn, storageHardwareService, hardwareIdInstance)) except Exception as ex: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) + LOG.error(_LE("Exception: %s"), ex) errorMessage = (_( "Unable to get target endpoints for hardwareId " - "%(hardwareIdInstance)s") + "%(hardwareIdInstance)s.") % {'hardwareIdInstance': hardwareIdInstance}) LOG.error(errorMessage) raise exception.VolumeBackendAPIException(data=errorMessage) @@ -1378,8 +1481,8 @@ class EMCVMAXCommon(object): if targetEndpoints: endpoints = targetEndpoints['TargetEndpoints'] - LOG.debug("There are %(len)lu endpoints " - % {'len': len(endpoints)}) + LOG.debug("There are %(len)lu endpoints.", + {'len': len(endpoints)}) for targetendpoint in endpoints: wwn = targetendpoint['Name'] # Add target wwn to the list if it is not already there. @@ -1387,12 +1490,12 @@ class EMCVMAXCommon(object): targetWwns.append(wwn) else: LOG.error(_LE( - "Target end points do not exist for hardware Id : " - "%(hardwareIdInstance)s ") - % {'hardwareIdInstance': hardwareIdInstance}) + "Target end points do not exist for hardware Id: " + "%(hardwareIdInstance)s."), + {'hardwareIdInstance': hardwareIdInstance}) - LOG.debug("Target WWNs: : %(targetWwns)s " - % {'targetWwns': targetWwns}) + LOG.debug("Target WWNs: %(targetWwns)s.", + {'targetWwns': targetWwns}) return targetWwns @@ -1426,9 +1529,9 @@ class EMCVMAXCommon(object): break LOG.debug("Storage Hardware IDs for %(wwpns)s is " - "%(foundInstances)s." - % {'wwpns': wwpns, - 'foundInstances': foundHardwareIdList}) + "%(foundInstances)s.", + {'wwpns': wwpns, + 'foundInstances': foundHardwareIdList}) return foundHardwareIdList @@ -1465,21 +1568,26 @@ class EMCVMAXCommon(object): :raises: VolumeBackendAPIException """ if os.path.isfile(configurationFile): - LOG.debug("Configuration file : %(configurationFile)s exists" - % {'configurationFile': configurationFile}) + LOG.debug("Configuration file : %(configurationFile)s exists.", + {'configurationFile': configurationFile}) else: exceptionMessage = (_( - "Configuration file %(configurationFile)s does not exist ") + "Configuration file %(configurationFile)s does not exist.") % {'configurationFile': configurationFile}) 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.url = 'http://' + ip + ':' + port + self.ecomUseSSL, self.ecomCACert, self.ecomNoVerification = ( + self.utils.get_ecom_cred_SSL(configurationFile)) + if self.ecomUseSSL: + self.url = 'https://' + ip + ':' + port + else: + self.url = 'http://' + ip + ':' + port self.conn = self._get_ecom_connection() - def _initial_setup(self, volume): + def _initial_setup(self, volume, volumeTypeId=None): """Necessary setup to accumulate the relevant information. The volume object has a host in which we can parse the @@ -1496,33 +1604,8 @@ class EMCVMAXCommon(object): """ try: extraSpecs, configurationFile = ( - self._set_config_file_and_get_extra_specs(volume)) - poolName = None - - try: - stripedMetaCount = extraSpecs[STRIPECOUNT] - extraSpecs[MEMBERCOUNT] = stripedMetaCount - extraSpecs[COMPOSITETYPE] = STRIPED - - LOG.debug( - "There are: %(stripedMetaCount)s striped metas in " - "the extra specs" - % {'stripedMetaCount': stripedMetaCount}) - except Exception: - memberCount = '1' - extraSpecs[MEMBERCOUNT] = memberCount - 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) + self._set_config_file_and_get_extra_specs( + volume, volumeTypeId)) arrayName = self.utils.parse_array_name_from_file( configurationFile) @@ -1530,41 +1613,27 @@ class EMCVMAXCommon(object): 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 ")) + "the EMC configuration file for the default case.")) 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: - LOG.debug("The fast policy name is : %(fastPolicyName)s. " - % {'fastPolicyName': fastPolicyName}) - - extraSpecs[POOL] = poolName - extraSpecs[ARRAY] = arrayName - extraSpecs[FASTPOLICY] = fastPolicyName - - LOG.debug("Pool is: %(pool)s " - "Array is: %(array)s " - "FastPolicy is: %(fastPolicy)s " - "CompositeType is: %(compositeType)s " - "MemberCount is: %(memberCount)s " - % {'pool': extraSpecs[POOL], - 'array': extraSpecs[ARRAY], - 'fastPolicy': extraSpecs[FASTPOLICY], - 'compositeType': extraSpecs[COMPOSITETYPE], - 'memberCount': extraSpecs[MEMBERCOUNT]}) + isV3 = self.utils.isArrayV3(self.conn, arrayName) + if isV3: + extraSpecs = self._set_v3_extra_specs( + extraSpecs, configurationFile, arrayName) + else: + # V2 extra specs. + extraSpecs = self._set_v2_extra_specs( + extraSpecs, configurationFile, arrayName) except Exception: 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 should be in the following format " - "/etc/cinder/cinder_emc_config_.xml")) + "/etc/cinder/cinder_emc_config_.xml.")) raise exception.VolumeBackendAPIException(data=exceptionMessage) return extraSpecs @@ -1580,16 +1649,16 @@ class EMCVMAXCommon(object): try: array = extraSpecs[ARRAY] poolInstanceName, storageSystemStr = self._find_pool_in_array( - array, extraSpecs[POOL]) + array, extraSpecs[POOL], extraSpecs[ISV3]) except Exception: exceptionMessage = (_( - "You must supply an array in your EMC configuration file ")) + "You must supply an array in your EMC configuration file.")) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) if poolInstanceName is None or storageSystemStr is None: exceptionMessage = (_( - "Cannot get necessary pool or storage system information ")) + "Cannot get necessary pool or storage system information.")) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -1606,21 +1675,40 @@ class EMCVMAXCommon(object): maskingViewDict = {} hostName = connector['host'] poolName = extraSpecs[POOL] - volumeName = volume['name'] + isV3 = extraSpecs[ISV3] + maskingViewDict['isV3'] = isV3 protocol = self.utils.get_short_protocol_type(self.protocol) - shortHostName = self.utils.get_host_short_name(hostName) + if isV3: + slo = extraSpecs[SLO] + workload = extraSpecs[WORKLOAD] + maskingViewDict['slo'] = slo + maskingViewDict['workload'] = workload + maskingViewDict['pool'] = poolName + maskingViewDict['sgGroupName'] = ( + 'OS-' + shortHostName + '-' + poolName + '-' + slo + '-' + + workload + '-SG') + maskingViewDict['maskingViewName'] = ( + 'OS-' + shortHostName + '-' + poolName + '-' + slo + '-' + + workload + '-MV') + else: + maskingViewDict['sgGroupName'] = ( + 'OS-' + shortHostName + '-' + poolName + '-' + + protocol + '-SG') + maskingViewDict['maskingViewName'] = ( + 'OS-' + shortHostName + '-' + poolName + '-' + + protocol + '-MV') + maskingViewDict['fastPolicy'] = ( + self.utils.parse_fast_policy_name_from_file( + self.configuration.cinder_emc_config_file)) + volumeName = volume['name'] volumeInstance = self._find_lun(volume) storageSystemName = volumeInstance['SystemName'] maskingViewDict['controllerConfigService'] = ( self.utils.find_controller_configuration_service( self.conn, storageSystemName)) - maskingViewDict['sgGroupName'] = ( - 'OS-' + shortHostName + '-' + poolName + '-' + protocol + '-SG') - maskingViewDict['maskingViewName'] = ( - 'OS-' + shortHostName + '-' + poolName + '-' + protocol + '-MV') # The portGroup is gotten from emc xml config file. maskingViewDict['pgGroupName'] = ( self.utils.parse_file_to_get_port_group_name( @@ -1631,9 +1719,6 @@ class EMCVMAXCommon(object): maskingViewDict['connector'] = connector maskingViewDict['volumeInstance'] = volumeInstance maskingViewDict['volumeName'] = volumeName - maskingViewDict['fastPolicy'] = ( - self.utils.parse_fast_policy_name_from_file( - self.configuration.cinder_emc_config_file)) maskingViewDict['storageSystemName'] = storageSystemName return maskingViewDict @@ -1644,7 +1729,7 @@ class EMCVMAXCommon(object): """Add the volume to the default storage group for that policy. On a create when fast policy is enable add the volume to the default - storage group for that policy. If it fails do the necessary rollback + storage group for that policy. If it fails do the necessary rollback. :param volumeDict: the volume dictionary :param volumeName: the volume name (String) @@ -1669,8 +1754,8 @@ class EMCVMAXCommon(object): if foundStorageGroupInstanceName is None: exceptionMessage = (_( - "Error adding Volume: %(volumeName)s. " - "with instance path: %(volumeInstancePath)s. ") + "Error adding Volume: %(volumeName)s " + "with instance path: %(volumeInstancePath)s.") % {'volumeName': volumeName, 'volumeInstancePath': volumeInstance.path}) LOG.error(exceptionMessage) @@ -1679,9 +1764,9 @@ class EMCVMAXCommon(object): except Exception as e: # Rollback by deleting the volume if adding the volume to the # default storage group were to fail. - LOG.error(_LE("Exception: %s") % six.text_type(e)) + LOG.error(_LE("Exception: %s"), e) errorMessage = (_( - "Rolling back %(volumeName)s by deleting it. ") + "Rolling back %(volumeName)s by deleting it.") % {'volumeName': volumeName}) LOG.error(errorMessage) self.provision.delete_volume_from_pool( @@ -1695,7 +1780,7 @@ class EMCVMAXCommon(object): """Create an unbound volume. Create an unbound volume so it is in the correct state to add to a - composite volume + composite volume. :param conn: the connection information to the ecom server :param storageConfigService: the storage config service instance name @@ -1731,9 +1816,10 @@ class EMCVMAXCommon(object): :params volumeSize: the size to create the volume :returns: volumeInstance the volume instance """ - volumeDict, rc = self.provision.create_volume_from_pool( - self.conn, storageConfigService, volumeName, poolInstanceName, - volumeSize) + volumeDict, _ = ( + self.provision.create_volume_from_pool( + self.conn, storageConfigService, volumeName, poolInstanceName, + volumeSize)) volumeInstance = self.utils.find_volume_instance( self.conn, volumeDict, volumeName) return volumeInstance @@ -1751,9 +1837,11 @@ class EMCVMAXCommon(object): :returns: unboundVolumeInstance the unbound volume instance """ - rc, job = self.provision.unbind_volume_from_storage_pool( - conn, storageConfigService, poolInstanceName, volumeInstanceName, - volumeName) + _, job = ( + self.provision.unbind_volume_from_storage_pool( + conn, storageConfigService, poolInstanceName, + volumeInstanceName, + volumeName)) volumeDict = self.provision.get_volume_dict_from_job(conn, job['Job']) volumeInstance = self.utils.find_volume_instance( self.conn, volumeDict, volumeName) @@ -1786,11 +1874,10 @@ class EMCVMAXCommon(object): conn, elementCompositionServiceInstanceName, volumeInstance.path, appendVolumeInstanceName, compositeType) else: - exception_message = (_( + LOG.error(_LE( "Unable to determine whether %(volumeName)s is " - "composite or not ") - % {'volumeName': volumeName}) - LOG.error(exception_message) + "composite or not."), + {'volumeName': volumeName}) raise modifiedVolumeDict = self.provision.get_volume_dict_from_job( @@ -1835,216 +1922,96 @@ class EMCVMAXCommon(object): sourceName = sourceVolume['name'] cloneName = cloneVolume['name'] - LOG.info(_LI("Create a Clone from Volume: Clone " - "Volume: %(cloneName)s " - "Source Volume: %(sourceName)s") - % {'cloneName': cloneName, - 'sourceName': sourceName}) + LOG.info(_LI( + "Create a replica from Volume: Clone Volume: %(cloneName)s " + "Source Volume: %(sourceName)s."), + {'cloneName': cloneName, + 'sourceName': sourceName}) self.conn = self._get_ecom_connection() sourceInstance = self._find_lun(sourceVolume) storageSystem = sourceInstance['SystemName'] + repServiceInstanceName = self.utils.find_replication_service( self.conn, storageSystem) - LOG.debug("Create Cloned Volume: Volume: %(cloneName)s " - "Source Volume: %(sourceName)s " - "Method: CreateElementReplica " + LOG.debug("Create volume replica: Volume: %(cloneName)s " + "Source Volume: %(sourceName)s " + "Method: CreateElementReplica " "ReplicationService: %(service)s ElementName: " "%(elementname)s SyncType: 8 SourceElement: " - "%(sourceelement)s" - % {'cloneName': cloneName, - 'sourceName': sourceName, - 'service': repServiceInstanceName, - 'elementname': cloneName, - 'sourceelement': sourceInstance.path}) - - return self._examine_source_and_create_clone( - repServiceInstanceName, cloneVolume, sourceVolume, - sourceInstance, extraSpecs) - - def _examine_source_and_create_clone(self, repServiceInstanceName, - cloneVolume, sourceVolume, - sourceInstance, extraSpecs): - """Create a clone (v2). - - :param repServiceInstanceName: the replication service - :param cloneVolume: the clone volume object - :param sourceVolume: the source volume object - :param sourceInstance: the device ID of the volume - :param fastPolicyName: the FAST policy name(if it exists) - :returns: rc - """ - # Check if the source volume contains any meta devices. - metaHeadInstanceName = self.utils.get_volume_meta_head( - self.conn, sourceInstance.path) - - if metaHeadInstanceName is None: # simple volume - return self._create_replica_and_delete_clone_relationship( - repServiceInstanceName, cloneVolume, sourceVolume, - sourceInstance, None, extraSpecs) - else: # Composite volume with meta device members - # Check if the meta members' capacity. - metaMemberInstanceNames = ( - self.utils.get_meta_members_of_composite_volume( - self.conn, metaHeadInstanceName)) - volumeCapacities = self.utils.get_meta_members_capacity_in_bit( - self.conn, metaMemberInstanceNames) - LOG.debug("Volume capacities: %(metasizes)s " - % {'metasizes': volumeCapacities}) - if len(set(volumeCapacities)) == 1: - LOG.debug("Meta volume all of the same size") - return self._create_replica_and_delete_clone_relationship( - repServiceInstanceName, cloneVolume, sourceVolume, - sourceInstance, None, extraSpecs) - - LOG.debug("Meta volumes are of different sizes: " - "%d different sizes." % len(set(volumeCapacities))) - - baseTargetVolumeInstance = None - for volumeSizeInbits in volumeCapacities: - if baseTargetVolumeInstance is None: # Create base volume - baseVolumeName = "TargetBaseVol" - volume = {'size': int(self.utils.convert_bits_to_gbs( - volumeSizeInbits))} - rc, baseVolumeDict, storageSystemName = ( - self._create_composite_volume( - volume, extraSpecs, - baseVolumeName, volumeSizeInbits)) - baseTargetVolumeInstance = self.utils.find_volume_instance( - self.conn, baseVolumeDict, baseVolumeName) - LOG.info(_LI("Base target volume %(targetVol)s created. " - "Capacity in bits: %(capInBits)lu ") - % {'capInBits': volumeSizeInbits, - 'targetVol': baseTargetVolumeInstance.path}) - else: # Create append volume. - targetVolumeName = "MetaVol" - volume = {'size': int(self.utils.convert_bits_to_gbs( - volumeSizeInbits))} - storageConfigService = ( - self.utils.find_storage_configuration_service( - self.conn, storageSystemName)) - unboundVolumeInstance = ( - self._create_and_get_unbound_volume( - self.conn, storageConfigService, - baseTargetVolumeInstance.path, volumeSizeInbits)) - if unboundVolumeInstance is None: - exceptionMessage = (_( - "Error Creating unbound volume.")) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - - # Append the new unbound volume to the - # base target composite volume. - baseTargetVolumeInstance = self.utils.find_volume_instance( - self.conn, baseVolumeDict, baseVolumeName) - elementCompositionService = ( - self.utils.find_element_composition_service( - self.conn, storageSystemName)) - compositeType = self.utils.get_composite_type( - extraSpecs[COMPOSITETYPE]) - rc, modifiedVolumeDict = ( - self._modify_and_get_composite_volume_instance( - self.conn, elementCompositionService, - baseTargetVolumeInstance, - unboundVolumeInstance.path, - targetVolumeName, compositeType)) - if modifiedVolumeDict is None: - exceptionMessage = (_( - "Error appending volume %(volumename)s to " - "target base volume") - % {'volumename': targetVolumeName}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - - LOG.debug("Create replica for meta members of different sizes.") - return self._create_replica_and_delete_clone_relationship( - repServiceInstanceName, cloneVolume, sourceVolume, - sourceInstance, baseTargetVolumeInstance, extraSpecs) + "%(sourceelement)s.", + {'cloneName': cloneName, + 'sourceName': sourceName, + 'service': repServiceInstanceName, + 'elementname': cloneName, + 'sourceelement': sourceInstance.path}) + + if extraSpecs[ISV3]: + rc, cloneDict = self._create_replica_v3(repServiceInstanceName, + cloneVolume, + sourceVolume, + sourceInstance, + isSnapshot) + else: + rc, cloneDict = self._create_clone_v2(repServiceInstanceName, + cloneVolume, + sourceVolume, + sourceInstance, + isSnapshot, + extraSpecs) + LOG.debug("Leaving _create_cloned_volume: Volume: " + "%(cloneName)s Source Volume: %(sourceName)s " + "Return code: %(rc)lu.", + {'cloneName': cloneName, + 'sourceName': sourceName, + 'rc': rc}) - def _create_replica_and_delete_clone_relationship( - self, repServiceInstanceName, cloneVolume, sourceVolume, - sourceInstance, targetInstance, extraSpecs): - """Helper function to create a clone and delete the relationship. + return cloneDict - This function creates clone of the source volume and then - delete the clone replatinship once the copy completes. + def _add_clone_to_default_storage_group( + self, fastPolicyName, storageSystemName, cloneDict, cloneName): + """Helper function to add clone to the default storage group. - :param repServiceInstanceName: replication service instance name - :param cloneVolume: target volume of the clone operation - :param sourceVolume: source volume of the clone operation - :param sourceInstance: instance of ECOM StorageVolume object - :param targetInstance: instance of ECOM StorageVolume object - :param extraSpecs: extraSpecs info - :returns: rc the return code, cloneDict the cloned volume dictionary + :param fastPolicyName: the fast policy name + :param storageSystemName: the storage system name + :param cloneDict: clone dictionary + :param cloneName: clone name """ - sourceName = sourceVolume['name'] - cloneName = cloneVolume['name'] - # Create a Clone from source volume - rc, job = self.provision.create_element_replica( - self.conn, repServiceInstanceName, cloneName, sourceName, - sourceInstance, targetInstance) - - cloneDict = self.provision.get_volume_dict_from_job( - self.conn, job['Job']) - - cloneVolume['provider_location'] = six.text_type(cloneDict) - syncInstanceName, storageSystemName = ( - self._find_storage_sync_sv_sv(cloneVolume, sourceVolume)) - - # Remove the Clone relationship so it can be used as a regular lun. - # 8 - Detach operation. - rc, job = self.provision.delete_clone_relationship( - self.conn, repServiceInstanceName, syncInstanceName, cloneName, - sourceName) + # Check if the clone/snapshot volume already part of the default sg. + cloneInstance = self.utils.find_volume_instance( + self.conn, cloneDict, cloneName) + if self.fast.is_volume_in_default_SG(self.conn, cloneInstance.path): + return - # If FAST enabled place clone volume or volume from snapshot to + # FAST enabled place clone volume or volume from snapshot to # default storage group. - if extraSpecs[FASTPOLICY] is not None: - LOG.debug("Adding volume: %(cloneName)s to default storage group " - "for FAST policy: %(fastPolicyName)s " - % {'cloneName': cloneName, - 'fastPolicyName': extraSpecs[FASTPOLICY]}) - - storageConfigService = ( - self.utils.find_storage_configuration_service( - self.conn, storageSystemName)) - - defaultStorageGroupInstanceName = ( - self._get_or_create_default_storage_group( - self.conn, storageSystemName, cloneDict, cloneName, - extraSpecs[FASTPOLICY])) - if defaultStorageGroupInstanceName is None: - exceptionMessage = (_( - "Unable to create or get default storage group for FAST " - "policy: %(fastPolicyName)s. ") - % {'fastPolicyName': extraSpecs[FASTPOLICY]}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) + LOG.debug("Adding volume: %(cloneName)s to default storage group " + "for FAST policy: %(fastPolicyName)s.", + {'cloneName': cloneName, + 'fastPolicyName': fastPolicyName}) - # Check if the clone/snapshot volume already part of the default - # sg. - cloneInstance = self.utils.find_volume_instance( - self.conn, cloneDict, cloneName) - inDefaultSG = self.fast.is_volume_in_default_SG( - self.conn, cloneInstance.path) - if inDefaultSG is False: - self._add_volume_to_default_storage_group_on_create( - cloneDict, cloneName, storageConfigService, - storageSystemName, extraSpecs[FASTPOLICY]) + storageConfigService = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystemName)) - LOG.debug("Leaving _create_cloned_volume: Volume: " - "%(cloneName)s Source Volume: %(sourceName)s " - "Return code: %(rc)lu." - % {'cloneName': cloneName, - 'sourceName': sourceName, - 'rc': rc}) + defaultStorageGroupInstanceName = ( + self._get_or_create_default_storage_group( + self.conn, storageSystemName, cloneDict, cloneName, + fastPolicyName)) + if defaultStorageGroupInstanceName is None: + exceptionMessage = (_( + "Unable to create or get default storage group for FAST " + "policy: %(fastPolicyName)s.") + % {'fastPolicyName': fastPolicyName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) - return cloneDict + self._add_volume_to_default_storage_group_on_create( + cloneDict, cloneName, storageConfigService, storageSystemName, + fastPolicyName) def _delete_volume(self, volume): """Helper function to delete the specified volume. @@ -2062,85 +2029,27 @@ class EMCVMAXCommon(object): volumeInstance = self._find_lun(volume) if volumeInstance is None: - LOG.error(_LE("Volume %(name)s not found on the array. " - "No volume to delete.") - % {'name': volumeName}) + LOG.error(_LE( + "Volume %(name)s not found on the array. " + "No volume to delete."), + {'name': volumeName}) return errorRet - storageSystemName = volumeInstance['SystemName'] - - storageConfigservice = self.utils.find_storage_configuration_service( - self.conn, storageSystemName) - controllerConfigurationService = ( - self.utils.find_controller_configuration_service( - self.conn, storageSystemName)) + storageConfigService = self.utils.find_storage_configuration_service( + self.conn, volumeInstance['SystemName']) deviceId = volumeInstance['DeviceID'] - fastPolicyName = extraSpecs[FASTPOLICY] - if fastPolicyName is not None: - defaultStorageGroupInstanceName = ( - self.masking.remove_device_from_default_storage_group( - self.conn, controllerConfigurationService, - volumeInstance.path, volumeName, fastPolicyName)) - if defaultStorageGroupInstanceName is None: - warnMessage = (_( - "The volume: %(volumename)s. was not first part of the " - "default storage group for FAST policy %(fastPolicyName)s" - ".") - % {'volumename': volumeName, - 'fastPolicyName': fastPolicyName}) - LOG.warn(warnMessage) - # Check if it is part of another storage group. - self._remove_device_from_storage_group( - controllerConfigurationService, volumeInstance.path, - volumeName) - + if extraSpecs[ISV3]: + storageGroupName = self.utils.get_v3_storage_group_name( + extraSpecs[POOL], extraSpecs[SLO], extraSpecs[WORKLOAD]) + rc = self._delete_from_pool_v3( + storageConfigService, volumeInstance, volumeName, + deviceId, storageGroupName) else: - # Check if volume is part of a storage group. - self._remove_device_from_storage_group( - controllerConfigurationService, - volumeInstance.path, volumeName) - - LOG.debug("Delete Volume: %(name)s Method: EMCReturnToStoragePool " - "ConfigServic: %(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( - self.conn, storageConfigservice, volumeInstance.path, - volumeName) - - except Exception as e: - # If we cannot successfully delete the volume then we want to - # return the volume to the default storage group. - if (fastPolicyName is not None and - defaultStorageGroupInstanceName is not None and - storageSystemName is not None): - assocDefaultStorageGroupName = ( - self.fast - .add_volume_to_default_storage_group_for_fast_policy( - self.conn, controllerConfigurationService, - volumeInstance, volumeName, fastPolicyName)) - if assocDefaultStorageGroupName is None: - errorMsg = (_LE( - "Failed to Roll back to re-add volume %(volumeName)s " - "to default storage group for fast policy " - "%(fastPolicyName)s: Please contact your sysadmin to " - "get the volume returned to the default storage group") - % {'volumeName': volumeName, - 'fastPolicyName': fastPolicyName}) - LOG.error(errorMsg) - - LOG.error(_LE("Exception: %s") % six.text_type(e)) - errorMessage = (_("Failed to delete volume %(volumeName)s") - % {'volumeName': volumeName}) - LOG.error(errorMessage) - raise exception.VolumeBackendAPIException(data=errorMessage) - + rc = self._delete_from_pool(storageConfigService, volumeInstance, + volumeName, deviceId, + extraSpecs[FASTPOLICY]) return (rc, volumeName) def _remove_device_from_storage_group( @@ -2148,21 +2057,20 @@ class EMCVMAXCommon(object): volumeName): """Check is volume is part of a storage group prior to delete. - Log a warning if volume is part of storage group + Log a warning if volume is part of storage group. :param controllerConfigurationService: controller configuration service :param volumeInstanceName: volume instance name :param volumeName: volume name (string) """ - storageGroupInstanceNames = ( self.masking.get_associated_masking_groups_from_device( self.conn, volumeInstanceName)) if storageGroupInstanceNames: LOG.warn(_LW( - "Pre check for deletion " - "Volume: %(volumeName)s is part of a storage group " - "Attempting removal from %(storageGroupInstanceNames)s"), + "Pre check for deletion. " + "Volume: %(volumeName)s is part of a storage group. " + "Attempting removal from %(storageGroupInstanceNames)s."), {'volumeName': volumeName, 'storageGroupInstanceNames': storageGroupInstanceNames}) for storageGroupInstanceName in storageGroupInstanceNames: @@ -2200,7 +2108,6 @@ class EMCVMAXCommon(object): storageHardwareIdInstances = self.conn.Associators( controllerInstanceName, ResultClass='EMC_StorageHardwareID') - for storageHardwareIdInstance in storageHardwareIdInstances: # If EMC_StorageHardwareID matches the initiator, we # found the existing EMC_LunMaskingSCSIProtocolController. @@ -2234,17 +2141,17 @@ class EMCVMAXCommon(object): LOG.debug("LunMaskingSCSIProtocolController for storage system " "%(storage_system)s and initiator %(initiator)s is " - "%(ctrl)s." - % {'storage_system': storageSystemName, - 'initiator': initiators, - 'ctrl': foundControllerInstanceName}) + "%(ctrl)s.", + {'storage_system': storageSystemName, + 'initiator': initiators, + 'ctrl': foundControllerInstanceName}) return foundControllerInstanceName def get_num_volumes_mapped(self, volume, connector): """Returns how many volumes are in the same zone as the connector. Find out how many volumes are mapped to a host - associated to the LunMaskingSCSIProtocolController + associated to the LunMaskingSCSIProtocolController. :param volume: volume object to be deleted :param connector: volume object to be deleted @@ -2255,8 +2162,8 @@ class EMCVMAXCommon(object): vol_instance = self._find_lun(volume) if vol_instance is None: msg = (_("Volume %(name)s not found on the array. " - "Cannot determine if there are volumes mapped."), - {'name': volumename}) + "Cannot determine if there are volumes mapped.") + % {'name': volumename}) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) @@ -2267,10 +2174,10 @@ class EMCVMAXCommon(object): connector) LOG.debug("LunMaskingSCSIProtocolController for storage system " - "%(storage)s and %(connector)s is %(ctrl)s." - % {'storage': storage_system, - 'connector': connector, - 'ctrl': ctrl}) + "%(storage)s and %(connector)s is %(ctrl)s.", + {'storage': storage_system, + 'connector': connector, + 'ctrl': ctrl}) # Return 0 if masking view does not exist. if ctrl is None: @@ -2283,125 +2190,469 @@ class EMCVMAXCommon(object): numVolumesMapped = len(associators) LOG.debug("Found %(numVolumesMapped)d volumes on storage system " - "%(storage)s mapped to %(connector)s." - % {'numVolumesMapped': numVolumesMapped, - 'storage': storage_system, - 'connector': connector}) + "%(storage)s mapped to %(connector)s.", + {'numVolumesMapped': numVolumesMapped, + 'storage': storage_system, + 'connector': connector}) return numVolumesMapped - def get_target_wwns_from_masking_view( - self, storageSystem, volume, connector): - """Find target WWNs via the masking view. + def _delete_snapshot(self, snapshot): + """Helper function to delete the specified snapshot. - :param storageSystem: the storage system name - :param volume: volume to be attached - :param connector: the connector dict - :returns: targetWwns, the target WWN list + :param snapshot: snapshot object to be deleted + :returns: None """ - targetWwns = [] - mvInstanceName = self.get_masking_view_by_volume(volume, connector) - if mvInstanceName is not None: - targetWwns = self.masking.get_target_wwns( - self.conn, mvInstanceName) - LOG.info(_LI("Target wwns in masking view %(maskingView)s: " - "%(targetWwns)s"), - {'maskingView': mvInstanceName, - 'targetWwns': str(targetWwns)}) - return targetWwns + LOG.debug("Entering delete_snapshot.") - def get_port_group_from_masking_view(self, maskingViewInstanceName): - """Find port group that is part of a masking view. + snapshotname = snapshot['name'] + LOG.info(_LI("Delete Snapshot: %(snapshot)s."), + {'snapshot': snapshotname}) - :param maskingViewInstanceName: the owning masking view - :returns: port group instance name - """ - return self.masking.get_port_group_from_masking_view( - self.conn, maskingViewInstanceName) + extraSpecs = self._initial_setup(snapshot) + self.conn = self._get_ecom_connection() - def get_masking_view_by_volume(self, volume, connector): - """Given volume, retrieve the masking view instance name + if not extraSpecs[ISV3]: + snapshotInstance = self._find_lun(snapshot) + storageSystem = snapshotInstance['SystemName'] - :param volume: the volume - :param connector: the connector object - :returns maskingviewInstanceName + # Wait for it to fully sync in case there is an ongoing + # create volume from snapshot request. + syncName = self.utils.find_sync_sv_by_target( + self.conn, storageSystem, snapshotInstance, True) + + if syncName is None: + LOG.info(_LI( + "Snapshot: %(snapshot)s: not found on the array."), + {'snapshot': snapshotname}) + else: + repservice = self.utils.find_replication_service(self.conn, + storageSystem) + if repservice is None: + exception_message = (_LE( + "Cannot find Replication Service to" + " delete snapshot %s.") % + snapshotname) + raise exception.VolumeBackendAPIException( + data=exception_message) + # Break the replication relationship + LOG.debug("Deleting snap relationship: Target: %(snapshot)s " + "Method: ModifyReplicaSynchronization " + "Replication Service: %(service)s Operation: 8 " + "Synchronization: %(syncName)s.", + {'snapshot': snapshotname, + 'service': str(repservice), + 'syncName': str(syncName)}) + + self.provision.delete_clone_relationship( + self.conn, repservice, syncName, True) + + # Delete the target device. + self._delete_volume(snapshot) + + def create_consistencygroup(self, context, group): + """Creates a consistencygroup. + + :param context: + :param group: the group object to be created + :returns: """ - LOG.debug("Finding Masking View for volume %(volume)s", - {'volume': volume}) - volumeInstance = self._find_lun(volume) - return self.masking.get_masking_view_by_volume( - self.conn, volumeInstance, connector) + LOG.info(_LI("Create Consistency Group: %(group)s."), + {'group': group['id']}) - def get_masking_views_by_port_group(self, portGroupInstanceName): - """Given port group, retrieve the masking view instance name. + modelUpdate = {'status': 'available'} + volumeTypeId = group['volume_type_id'].replace(",", "") - :param : the volume - :param mvInstanceName: masking view instance name - :returns: maksingViewInstanceNames - """ - LOG.debug("Finding Masking Views for port group %(pg)s" - % {'pg': portGroupInstanceName}) - return self.masking.get_masking_views_by_port_group( - self.conn, portGroupInstanceName) + cgName = self.utils.truncate_string(group['id'], 8) - def _create_composite_volume( - self, volume, extraSpecs, volumeName, volumeSize): - """Create a composite volume. + extraSpecs = self._initial_setup(None, volumeTypeId) - :param volume: the volume object - :param extraSpecs: - :param volumeName: - :param volumeSize: - :returns: - """ - memberCount, errorDesc = self.utils.determine_member_count( - volume['size'], extraSpecs[MEMBERCOUNT], extraSpecs[COMPOSITETYPE]) - if errorDesc is not None: - exceptionMessage = (_("The striped meta count of %(memberCount)s " - "is too small for volume: %(volumeName)s. " - "with size %(volumeSize)s ") - % {'memberCount': memberCount, - 'volumeName': volumeName, - 'volumeSize': volume['size']}) + _, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + + self.conn = self._get_ecom_connection() + + # Find storage system. + try: + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + self.provision.create_consistency_group( + self.conn, replicationService, cgName) + except Exception as ex: + LOG.error(_LE("Exception: %(ex)s"), {'ex': ex}) + exceptionMessage = (_("Failed to create consistency group:" + " %(cgName)s.") + % {'cgName': cgName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) - poolInstanceName, storageSystemName = ( - self._get_pool_and_storage_system(extraSpecs)) + return modelUpdate - LOG.debug("Create Volume: %(volume)s Pool: %(pool)s " - "Storage System: %(storageSystem)s " - "Size: %(size)lu " - % {'volume': volumeName, - 'pool': poolInstanceName, - 'storageSystem': storageSystemName, - 'size': volumeSize}) + def delete_consistencygroup(self, context, group, volumes): + """Deletes a consistency group. - elementCompositionService = ( - self.utils.find_element_composition_service(self.conn, - storageSystemName)) + :param context: + :param group: the group object to be deleted + :returns: + """ + LOG.info(_LI("Delete Consistency Group: %(group)s."), + {'group': group['id']}) - storageConfigService = self.utils.find_storage_configuration_service( - self.conn, storageSystemName) + cgName = self.utils.truncate_string(group['id'], 8) - # If FAST is intended to be used we must first check that the pool - # is associated with the correct storage tier. - if extraSpecs[FASTPOLICY] is not None: - foundPoolInstanceName = self.fast.get_pool_associated_to_policy( - self.conn, extraSpecs[FASTPOLICY], extraSpecs[ARRAY], - storageConfigService, poolInstanceName) - if foundPoolInstanceName is None: - exceptionMessage = (_("Pool: %(poolName)s. " - "is not associated to storage tier for " - "fast policy %(fastPolicy)s.") - % {'poolName': extraSpecs[POOL], - 'fastPolicy': extraSpecs[FASTPOLICY]}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) + modelUpdate = {} + modelUpdate['status'] = group['status'] + volumeTypeId = group['volume_type_id'].replace(",", "") - compositeType = self.utils.get_composite_type( - extraSpecs[COMPOSITETYPE]) + extraSpecs = self._initial_setup(None, volumeTypeId) + + _, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + + try: + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + + storageConfigservice = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystem)) + cgInstanceName = self._find_consistency_group( + replicationService, cgName) + if cgInstanceName is None: + exception_message = (_("Cannot find CG group %s.") % + cgName) + raise exception.VolumeBackendAPIException( + data=exception_message) + + memberInstanceNames = self._get_members_of_replication_group( + cgInstanceName) + + self.provision.delete_consistency_group(self.conn, + replicationService, + cgInstanceName, cgName) + + # Do a bulk delete, a lot faster than single deletes. + if memberInstanceNames: + volumes, modelUpdate = self._do_bulk_delete( + storageSystem, memberInstanceNames, storageConfigservice, + volumes, modelUpdate, extraSpecs[ISV3]) + + except Exception as ex: + LOG.error(_LE("Exception: %s"), ex) + exceptionMessage = (_( + "Failed to delete consistency group: %(cgName)s.") + % {'cgName': cgName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + return modelUpdate, volumes + + def _do_bulk_delete(self, storageSystem, memberInstanceNames, + storageConfigservice, volumes, modelUpdate, isV3): + """Do a bulk delete. + + :param storageSystem: storage system name + :param memberInstanceNames: volume Instance names + :param storageConfigservice: storage config service + :param volumes: volume objects + :param modelUpdate: + :param isV3: true/false + :returns: volumes, modelUpdate + """ + try: + controllerConfigurationService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystem)) + for memberInstanceName in memberInstanceNames: + self._remove_device_from_storage_group( + controllerConfigurationService, memberInstanceName, + 'Member Volume') + if isV3: + self.provisionv3.delete_volume_from_pool( + self.conn, storageConfigservice, + memberInstanceNames, None) + else: + self.provision.delete_volume_from_pool( + self.conn, storageConfigservice, + memberInstanceNames, None) + for volumeRef in volumes: + volumeRef['status'] = 'deleted' + except Exception: + for volumeRef in volumes: + volumeRef['status'] = 'error_deleting' + modelUpdate['status'] = 'error_deleting' + return volumes, modelUpdate + + def create_cgsnapshot(self, context, cgsnapshot, db): + """Creates a cgsnapshot. + + :param context: + :param cgsnapshot: the consistency group snapshot to be created + :param db: cinder database + :returns: modelUpdate, list of snapshots + """ + consistencyGroup = db.consistencygroup_get( + context, cgsnapshot['consistencygroup_id']) + + LOG.info(_LI( + "Create snapshot for Consistency Group %(cgId)s " + "cgsnapshotID: %(cgsnapshot)s."), + {'cgsnapshot': cgsnapshot['id'], + 'cgId': cgsnapshot['consistencygroup_id']}) + + cgName = self.utils.truncate_string( + cgsnapshot['consistencygroup_id'], 8) + + modelUpdate = {'status': 'available'} + + volumeTypeId = consistencyGroup['volume_type_id'].replace(",", "") + extraSpecs = self._initial_setup(None, volumeTypeId) + self.conn = self._get_ecom_connection() + + _, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + + try: + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + + cgInstanceName = ( + self._find_consistency_group(replicationService, cgName)) + if cgInstanceName is None: + exception_message = (_("Cannot find CG group %s.") % cgName) + raise exception.VolumeBackendAPIException( + data=exception_message) + + memberInstanceNames = self._get_members_of_replication_group( + cgInstanceName) + + # Create the target consistency group. + targetCgName = self.utils.truncate_string(cgsnapshot['id'], 8) + self.provision.create_consistency_group( + self.conn, replicationService, targetCgName) + targetCgInstanceName = self._find_consistency_group( + replicationService, targetCgName) + LOG.info(_LI("Create target consistency group %(targetCg)s."), + {'targetCg': targetCgInstanceName}) + + for memberInstanceName in memberInstanceNames: + volInstance = self.conn.GetInstance( + memberInstanceName, LocalOnly=False) + numOfBlocks = volInstance['NumberOfBlocks'] + blockSize = volInstance['BlockSize'] + volumeSizeInbits = numOfBlocks * blockSize + + targetVolumeName = 'targetVol' + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + + if extraSpecs[ISV3]: + _, volumeDict, _ = ( + self._create_v3_volume( + volume, extraSpecs, + targetVolumeName, volumeSizeInbits)) + else: + _, volumeDict, _ = ( + self._create_composite_volume( + volume, extraSpecs, + targetVolumeName, volumeSizeInbits)) + targetVolumeInstance = self.utils.find_volume_instance( + self.conn, volumeDict, targetVolumeName) + LOG.debug("Create target volume for member volume " + "source volume: %(memberVol)s " + "target volume %(targetVol)s.", + {'memberVol': memberInstanceName, + 'targetVol': targetVolumeInstance.path}) + self.provision.add_volume_to_cg(self.conn, + replicationService, + targetCgInstanceName, + targetVolumeInstance.path, + targetCgName, + targetVolumeName) + + # Less than 5 characters relationship name. + relationName = self.utils.truncate_string(cgsnapshot['id'], 5) + if extraSpecs[ISV3]: + self.provisionv3.create_group_replica( + self.conn, replicationService, cgInstanceName, + targetCgInstanceName, relationName) + else: + self.provision.create_group_replica( + self.conn, replicationService, cgInstanceName, + targetCgInstanceName, relationName) + # Break the replica group relationship. + rgSyncInstanceName = self.utils.find_group_sync_rg_by_target( + self.conn, storageSystem, targetCgInstanceName, True) + if rgSyncInstanceName is not None: + repservice = self.utils.find_replication_service( + self.conn, storageSystem) + if repservice is None: + exception_message = (_( + "Cannot find Replication service on system %s.") % + storageSystem) + raise exception.VolumeBackendAPIException( + data=exception_message) + if extraSpecs[ISV3]: + # Operation 7: dissolve for snapVx. + operation = self.utils.get_num(9, '16') + self.provisionv3.break_replication_relationship( + self.conn, repservice, rgSyncInstanceName, operation) + else: + self.provision.delete_clone_relationship(self.conn, repservice, + rgSyncInstanceName) + + except Exception as ex: + modelUpdate['status'] = 'error' + self.utils.populate_cgsnapshot_status( + context, db, cgsnapshot['id'], modelUpdate['status']) + LOG.error(_LE("Exception: %(ex)s"), {'ex': ex}) + exceptionMessage = (_("Failed to create snapshot for cg:" + " %(cgName)s.") + % {'cgName': cgName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + snapshots = self.utils.populate_cgsnapshot_status( + context, db, cgsnapshot['id'], modelUpdate['status']) + return modelUpdate, snapshots + + def delete_cgsnapshot(self, context, cgsnapshot, db): + """Delete a cgsnapshot. + + :param context: + :param cgsnapshot: the consistency group snapshot to be created + :param db: cinder database + :returns: modelUpdate, list of snapshots + """ + consistencyGroup = db.consistencygroup_get( + context, cgsnapshot['consistencygroup_id']) + snapshots = db.snapshot_get_all_for_cgsnapshot( + context, cgsnapshot['id']) + + LOG.info(_LI( + "Delete snapshot for source CG %(cgId)s " + "cgsnapshotID: %(cgsnapshot)s."), + {'cgsnapshot': cgsnapshot['id'], + 'cgId': cgsnapshot['consistencygroup_id']}) + + modelUpdate = {'status': 'deleted'} + volumeTypeId = consistencyGroup['volume_type_id'].replace(",", "") + extraSpecs = self._initial_setup(None, volumeTypeId) + self.conn = self._get_ecom_connection() + + _, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + + try: + targetCgName = self.utils.truncate_string(cgsnapshot['id'], 8) + modelUpdate, snapshots = self._delete_cg_and_members( + storageSystem, extraSpecs, targetCgName, modelUpdate, + snapshots) + except Exception as ex: + modelUpdate['status'] = 'error_deleting' + self.utils.populate_cgsnapshot_status( + context, db, cgsnapshot['id'], modelUpdate['status']) + LOG.error(_LE("Exception: %(ex)s"), {'ex': ex}) + exceptionMessage = (_("Failed to delete snapshot for cg: " + "%(cgId)s.") + % {'cgId': cgsnapshot['consistencygroup_id']}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + snapshots = self.utils.populate_cgsnapshot_status( + context, db, cgsnapshot['id'], modelUpdate['status']) + return modelUpdate, snapshots + + def _find_consistency_group(self, replicationService, cgName): + """Finds a CG given its name. + + :param replicationService: the replication service + :param cgName: the consistency group name + :returns: + """ + foundCgInstanceName = None + cgInstanceNames = ( + self.conn.AssociatorNames(replicationService, + ResultClass='CIM_ReplicationGroup')) + + for cgInstanceName in cgInstanceNames: + instance = self.conn.GetInstance(cgInstanceName, LocalOnly=False) + if cgName == instance['ElementName']: + foundCgInstanceName = cgInstanceName + break + + return foundCgInstanceName + + def _get_members_of_replication_group(self, cgInstanceName): + """Get the members of consistency group. + + :param cgInstanceName: the CG instance name + :returns: + """ + memberInstanceNames = self.conn.AssociatorNames( + cgInstanceName, + AssocClass='CIM_OrderedMemberOfCollection') + + return memberInstanceNames + + def _create_composite_volume( + self, volume, extraSpecs, volumeName, volumeSize): + """Create a composite volume (V2). + + :param volume: the volume object + :param extraSpecs: + :param volumeName: + :param volumeSize: + :returns: + """ + memberCount, errorDesc = self.utils.determine_member_count( + volume['size'], extraSpecs[MEMBERCOUNT], extraSpecs[COMPOSITETYPE]) + if errorDesc is not None: + exceptionMessage = (_("The striped meta count of %(memberCount)s " + "is too small for volume: %(volumeName)s " + "with size %(volumeSize)s.") + % {'memberCount': memberCount, + 'volumeName': volumeName, + 'volumeSize': volume['size']}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + poolInstanceName, storageSystemName = ( + self._get_pool_and_storage_system(extraSpecs)) + + LOG.debug("Create Volume: %(volume)s Pool: %(pool)s " + "Storage System: %(storageSystem)s " + "Size: %(size)lu.", + {'volume': volumeName, + 'pool': poolInstanceName, + 'storageSystem': storageSystemName, + 'size': volumeSize}) + + elementCompositionService = ( + self.utils.find_element_composition_service(self.conn, + storageSystemName)) + + storageConfigService = self.utils.find_storage_configuration_service( + self.conn, storageSystemName) + + # If FAST is intended to be used we must first check that the pool + # is associated with the correct storage tier. + if extraSpecs[FASTPOLICY] is not None: + foundPoolInstanceName = self.fast.get_pool_associated_to_policy( + self.conn, extraSpecs[FASTPOLICY], extraSpecs[ARRAY], + storageConfigService, poolInstanceName) + if foundPoolInstanceName is None: + exceptionMessage = (_("Pool: %(poolName)s. " + "is not associated to storage tier for " + "fast policy %(fastPolicy)s.") + % {'poolName': extraSpecs[POOL], + 'fastPolicy': extraSpecs[FASTPOLICY]}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + compositeType = self.utils.get_composite_type( + extraSpecs[COMPOSITETYPE]) volumeDict, rc = self.provision.create_composite_volume( self.conn, elementCompositionService, volumeSize, volumeName, @@ -2412,10 +2663,11 @@ class EMCVMAXCommon(object): # add the volume to the default storage group created for # volumes in pools associated with this fast policy. if extraSpecs[FASTPOLICY]: - LOG.info(_LI("Adding volume: %(volumeName)s to default storage " - "group for FAST policy: %(fastPolicyName)s ") - % {'volumeName': volumeName, - 'fastPolicyName': extraSpecs[FASTPOLICY]}) + LOG.info(_LI( + "Adding volume: %(volumeName)s to default storage group" + " for FAST policy: %(fastPolicyName)s."), + {'volumeName': volumeName, + 'fastPolicyName': extraSpecs[FASTPOLICY]}) defaultStorageGroupInstanceName = ( self._get_or_create_default_storage_group( self.conn, storageSystemName, volumeDict, @@ -2423,7 +2675,7 @@ class EMCVMAXCommon(object): if not defaultStorageGroupInstanceName: exceptionMessage = (_( "Unable to create or get default storage group for " - "FAST policy: %(fastPolicyName)s. ") + "FAST policy: %(fastPolicyName)s.") % {'fastPolicyName': extraSpecs[FASTPOLICY]}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( @@ -2433,3 +2685,950 @@ class EMCVMAXCommon(object): volumeDict, volumeName, storageConfigService, storageSystemName, extraSpecs[FASTPOLICY]) return rc, volumeDict, storageSystemName + + def _create_v3_volume( + self, volume, extraSpecs, volumeName, volumeSize): + """create a volume (V3). + + :param volume: the volume object + :param extraSpecs: + :param volumeName: + :param volumeSize: + :returns: + """ + isValidSLO, isValidWorkload = self.utils.verify_slo_workload( + extraSpecs[SLO], extraSpecs[WORKLOAD]) + + if not isValidSLO or not isValidWorkload: + exceptionMessage = (_( + "Either SLO: %(slo)s or workload %(workload)s is invalid. " + "Examine previous error statement for valid values.") + % {'slo': extraSpecs[SLO], + 'workload': extraSpecs[WORKLOAD]}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + poolInstanceName, storageSystemName = ( + self._get_pool_and_storage_system(extraSpecs)) + + LOG.debug("Create Volume: %(volume)s Pool: %(pool)s " + "Storage System: %(storageSystem)s " + "Size: %(size)lu.", + {'volume': volumeName, + 'pool': poolInstanceName, + 'storageSystem': storageSystemName, + 'size': volumeSize}) + + 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])) + if not self.utils.is_in_range( + volumeSize, maximumVolumeSize, minimumVolumeSize): + LOG.warn(_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. + sgInstanceName = self._get_or_create_storage_group_v3( + extraSpecs[POOL], extraSpecs[SLO], extraSpecs[WORKLOAD], + storageSystemName) + volumeDict, rc = self.provisionv3.create_volume_from_sg( + self.conn, storageConfigService, volumeName, + sgInstanceName, volumeSize) + + return rc, volumeDict, storageSystemName + + def _get_or_create_storage_group_v3( + self, poolName, slo, workload, storageSystemName): + """Get or create storage group_v3 (V3). + + :param poolName: the SRP pool nsmr + :param slo: the SLO + :param workload: the workload + :param storageSystemName: storage system name + :returns: + """ + storageGroupName = self.utils.get_v3_storage_group_name( + poolName, slo, workload) + controllerConfigService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystemName)) + sgInstanceName = self.utils.find_storage_masking_group( + self.conn, controllerConfigService, storageGroupName) + if sgInstanceName is None: + sgInstanceName = self.provisionv3.create_storage_group_v3( + self.conn, controllerConfigService, storageGroupName, + poolName, slo, workload) + + return sgInstanceName + + def _extend_composite_volume(self, volumeInstance, volumeName, + newSize, additionalVolumeSize): + """Extend a composite volume (V2). + + :param volumeInstance: the volume instance + :param volumeName: the name of the volume + :param newSize: in GBs + :param additionalVolumeSize: + :returns: + """ + # Is the volume extendable. + isConcatenated = self.utils.check_if_volume_is_extendable( + self.conn, volumeInstance) + if 'True' not in isConcatenated: + exceptionMessage = (_( + "Volume: %(volumeName)s is not a concatenated volume. " + "You can only perform extend on concatenated volume. " + "Exiting...") + % {'volumeName': volumeName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + else: + compositeType = self.utils.get_composite_type(CONCATENATED) + + LOG.debug("Extend Volume: %(volume)s New size: %(newSize)s GBs.", + {'volume': volumeName, + 'newSize': newSize}) + + deviceId = volumeInstance['DeviceID'] + storageSystemName = volumeInstance['SystemName'] + LOG.debug( + "Device ID: %(deviceid)s: Storage System: " + "%(storagesystem)s.", + {'deviceid': deviceId, + 'storagesystem': storageSystemName}) + + storageConfigService = self.utils.find_storage_configuration_service( + self.conn, storageSystemName) + + elementCompositionService = ( + self.utils.find_element_composition_service( + self.conn, storageSystemName)) + + # Create a volume to the size of the + # newSize - oldSize = additionalVolumeSize. + unboundVolumeInstance = self._create_and_get_unbound_volume( + self.conn, storageConfigService, volumeInstance.path, + additionalVolumeSize) + if unboundVolumeInstance is None: + exceptionMessage = (_( + "Error Creating unbound volume on an Extend operation.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + # Add the new unbound volume to the original composite volume. + rc, modifiedVolumeDict = ( + self._modify_and_get_composite_volume_instance( + self.conn, elementCompositionService, volumeInstance, + unboundVolumeInstance.path, volumeName, compositeType)) + if modifiedVolumeDict is None: + exceptionMessage = (_( + "On an Extend Operation, error adding volume to composite " + "volume: %(volumename)s.") + % {'volumename': volumeName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + return rc, modifiedVolumeDict + + def _slo_workload_migration(self, volumeInstance, volume, host, + volumeName, volumeStatus, + extraSpecs, newType): + """Migrate from SLO/Workload combination to another (V3). + + :param volumeInstance: the volume instance + :param volume: the volume object + :param host: the host object + :param volumeName: the name of the volume + :param volumeStatus: the volume status + :param extraSpecs: the extra specs dict + :param newType: + :returns: boolean + """ + volumeInstanceName = volumeInstance.path + isValid, targetSlo, targetWorkload = ( + self._is_valid_for_storage_assisted_migration_v3( + volumeInstanceName, host, extraSpecs[ARRAY], + extraSpecs[POOL], volumeName, volumeStatus)) + + storageSystemName = volumeInstance['SystemName'] + + if not isValid: + LOG.error(_LE( + "Volume %(name)s is not suitable for storage " + "assisted migration using retype."), + {'name': volumeName}) + return False + if volume['host'] != host['host']: + LOG.debug( + "Retype Volume %(name)s from source host %(sourceHost)s " + "to target host %(targetHost)s.", + {'name': volumeName, + 'sourceHost': volume['host'], + 'targetHost': host['host']}) + return self._migrate_volume_v3( + volume, volumeInstance, extraSpecs[POOL], targetSlo, + targetWorkload, storageSystemName, newType) + + return False + + def _migrate_volume_v3( + self, volume, volumeInstance, poolName, targetSlo, + targetWorkload, storageSystemName, newType): + """Migrate from one slo/workload combination to another (V3). + + This requires moving the volume from it's current SG to a + new or existing SG that has the target attributes. + + :param volume: the volume object + :param volumeInstance: the volume instance + :param poolName: the SRP Pool Name + :param targetSlo: the target SLO + :param targetWorkload: the target workload + :param storageSystemName: the storage system name + :param newType: + :returns: boolean + """ + volumeName = volume['name'] + + controllerConfigService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystemName)) + + foundStorageGroupInstanceName = ( + self.utils.get_storage_group_from_volume( + self.conn, volumeInstance.path)) + if foundStorageGroupInstanceName is None: + LOG.warn(_LW( + "Volume : %(volumeName)s is not currently " + "belonging to any storage group."), + {'volumeName': volumeName}) + else: + self.provision.remove_device_from_storage_group( + self.conn, + controllerConfigService, + foundStorageGroupInstanceName, + volumeInstance.path, + volumeName) + # Check that it has been removed. + sgFromVolRemovedInstanceName = ( + self.utils.wrap_get_storage_group_from_volume( + self.conn, volumeInstance.path)) + if sgFromVolRemovedInstanceName is not None: + LOG.error(_LE( + "Volume : %(volumeName)s has not been " + "removed from source storage group %(storageGroup)s."), + {'volumeName': volumeName, + 'storageGroup': sgFromVolRemovedInstanceName}) + return False + + storageGroupName = self.utils.get_v3_storage_group_name( + poolName, targetSlo, targetWorkload) + + targetSgInstanceName = self._get_or_create_storage_group_v3( + poolName, targetSlo, targetWorkload, storageSystemName) + if targetSgInstanceName is None: + LOG.error(_LE( + "Failed to get or create storage group %(storageGroupName)s."), + {'storageGroupName': storageGroupName}) + return False + + self.masking.add_volume_to_storage_group( + self.conn, controllerConfigService, targetSgInstanceName, + volumeInstance, volumeName, storageGroupName) + # Check that it has been added. + sgFromVolAddedInstanceName = ( + self.utils.get_storage_group_from_volume( + self.conn, volumeInstance.path)) + if sgFromVolAddedInstanceName is None: + LOG.error(_LE( + "Volume : %(volumeName)s has not been " + "added to target storage group %(storageGroup)s."), + {'volumeName': volumeName, + 'storageGroup': targetSgInstanceName}) + return False + + return True + + def _pool_migration(self, volumeInstance, volume, host, + volumeName, volumeStatus, + fastPolicyName, newType): + """Migrate from one pool to another (V2). + + :param volumeInstance: the volume instance + :param volume: the volume object + :param host: the host object + :param volumeName: the name of the volume + :param volumeStatus: the volume status + :param fastPolicyName: the FAST policy Name + :param newType: + :returns: boolean + """ + storageSystemName = volumeInstance['SystemName'] + isValid, targetPoolName, targetFastPolicyName = ( + self._is_valid_for_storage_assisted_migration( + volumeInstance.path, host, storageSystemName, + volumeName, volumeStatus)) + + if not isValid: + LOG.error(_LE( + "Volume %(name)s is not suitable for storage " + "assisted migration using retype."), + {'name': volumeName}) + return False + if volume['host'] != host['host']: + LOG.debug( + "Retype Volume %(name)s from source host %(sourceHost)s " + "to target host %(targetHost)s.", + {'name': volumeName, + 'sourceHost': volume['host'], + 'targetHost': host['host']}) + return self._migrate_volume( + volume, volumeInstance, targetPoolName, targetFastPolicyName, + fastPolicyName, newType) + + return False + + def _update_pool_stats( + self, emcConfigFileName, backendName, arrayName, poolName): + """Update pool statistics (V2). + + :param emcConfigFileName: the EMC configuration file + :param backendName: the backend name + :param arrayName: the array Name + :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: + LOG.debug( + "Fast policy %(fastPolicyName)s is enabled on %(arrayName)s.", + {'fastPolicyName': fastPolicyName, + 'arrayName': arrayName}) + else: + LOG.debug( + "No Fast policy for Array:%(arrayName)s " + "backend:%(backendName)s.", + {'arrayName': arrayName, + 'backendName': backendName}) + + storageSystemInstanceName = self.utils.find_storageSystem( + self.conn, arrayName) + isTieringPolicySupported = ( + self.fast.is_tiering_policy_enabled_on_storage_system( + self.conn, storageSystemInstanceName)) + + if (fastPolicyName 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)) + 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, + '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)) + LOG.info(_LI( + "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, + '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}) + + return location_info, total_capacity_gb, free_capacity_gb + + def _set_v2_extra_specs(self, extraSpecs, configurationFile, arrayName): + """Set the VMAX V2 extra specs. + + :param extraSpecs: the extraSpecs (input) + :param configurationFile: the EMC configuration file + :param arrayName: the array serial number + :returns: extraSpecs (out) + """ + try: + stripedMetaCount = extraSpecs[STRIPECOUNT] + extraSpecs[MEMBERCOUNT] = stripedMetaCount + extraSpecs[COMPOSITETYPE] = STRIPED + + LOG.debug( + "There are: %(stripedMetaCount)s striped metas in " + "the extra specs.", + {'stripedMetaCount': stripedMetaCount}) + except KeyError: + memberCount = '1' + extraSpecs[MEMBERCOUNT] = memberCount + 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 doesnt want to associate with any FAST policy. + fastPolicyName = self.utils.parse_fast_policy_name_from_file( + configurationFile) + if fastPolicyName is not None: + LOG.debug("The fast policy name is: %(fastPolicyName)s.", + {'fastPolicyName': fastPolicyName}) + + extraSpecs[POOL] = poolName + extraSpecs[ARRAY] = arrayName + extraSpecs[FASTPOLICY] = fastPolicyName + extraSpecs[ISV3] = False + + LOG.debug("Pool is: %(pool)s " + "Array is: %(array)s " + "FastPolicy is: %(fastPolicy)s " + "CompositeType is: %(compositeType)s " + "MemberCount is: %(memberCount)s.", + {'pool': extraSpecs[POOL], + 'array': extraSpecs[ARRAY], + 'fastPolicy': extraSpecs[FASTPOLICY], + 'compositeType': extraSpecs[COMPOSITETYPE], + 'memberCount': extraSpecs[MEMBERCOUNT]}) + return extraSpecs + + def _set_v3_extra_specs(self, extraSpecs, configurationFile, arrayName): + """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 extraSpecs: the extraSpecs (input) + :param configurationFile: the EMC configuration file + :param arrayName: the array serial number + :returns: extraSpecs (out) + """ + 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[ISV3] = True + + LOG.debug("Pool is: %(pool)s " + "Array is: %(array)s " + "SLO is: %(slo)s " + "Workload is: %(workload)s.", + {'pool': extraSpecs[POOL], + 'array': extraSpecs[ARRAY], + 'slo': extraSpecs[SLO], + 'workload': extraSpecs[WORKLOAD]}) + return extraSpecs + + def _delete_from_pool(self, storageConfigService, volumeInstance, + volumeName, deviceId, fastPolicyName): + """Delete from pool (v2). + + :param storageConfigService: the storage config service + :param volumeInstance: the volume instance + :param volumeName: the volume Name + :param deviceId: the device ID of the volume + :param fastPolicyName: the FAST policy name(if it exists) + :returns: rc + """ + storageSystemName = volumeInstance['SystemName'] + controllerConfigurationService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystemName)) + if fastPolicyName is not None: + defaultStorageGroupInstanceName = ( + self.masking.remove_device_from_default_storage_group( + self.conn, controllerConfigurationService, + volumeInstance.path, volumeName, fastPolicyName)) + if defaultStorageGroupInstanceName is None: + LOG.warn(_LW( + "The volume: %(volumename)s. was not first part of the " + "default storage group for FAST policy %(fastPolicyName)s" + "."), + {'volumename': volumeName, + 'fastPolicyName': fastPolicyName}) + # Check if it is part of another storage group. + self._remove_device_from_storage_group( + controllerConfigurationService, + volumeInstance.path, volumeName) + + else: + # Check if volume is part of a storage group. + self._remove_device_from_storage_group( + controllerConfigurationService, + volumeInstance.path, 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( + self.conn, storageConfigService, volumeInstance.path, + volumeName) + + except Exception as e: + # If we cannot successfully delete the volume then we want to + # return the volume to the default storage group. + if (fastPolicyName is not None and + defaultStorageGroupInstanceName is not None and + storageSystemName is not None): + assocDefaultStorageGroupName = ( + self.fast + .add_volume_to_default_storage_group_for_fast_policy( + self.conn, controllerConfigurationService, + volumeInstance, volumeName, fastPolicyName)) + if assocDefaultStorageGroupName is None: + LOG.error(_LE( + "Failed to Roll back to re-add volume %(volumeName)s " + "to default storage group for fast policy " + "%(fastPolicyName)s. Please contact your sysadmin to " + "get the volume returned to the default " + "storage group."), + {'volumeName': volumeName, + 'fastPolicyName': fastPolicyName}) + + LOG.error(_LE("Exception: %s."), e) + errorMessage = (_("Failed to delete volume %(volumeName)s.") % + {'volumeName': volumeName}) + LOG.error(errorMessage) + raise exception.VolumeBackendAPIException(data=errorMessage) + + return rc + + def _delete_from_pool_v3(self, storageConfigService, volumeInstance, + volumeName, deviceId, storageGroupName): + """Delete from pool (v3). + + :param storageConfigService: the storage config service + :param volumeInstance: the volume instance + :param volumeName: the volume Name + :param deviceId: the device ID of the volume + :param storageGroupName: the name of the default SG + :returns: rc + """ + storageSystemName = volumeInstance['SystemName'] + controllerConfigurationService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystemName)) + + # Check if it is part of a storage group and delete it + # extra logic for case when volume is the last member. + sgFromVolInstanceName = self.masking.remove_and_reset_members( + self.conn, controllerConfigurationService, volumeInstance, + None, volumeName, True, None, 'noReset') + + LOG.debug("Delete Volume: %(name)s Method: EMCReturnToStoragePool " + "ConfigServic: %(service)s TheElement: %(vol_instance)s " + "DeviceId: %(deviceId)s.", + {'service': storageConfigService, + 'name': volumeName, + 'vol_instance': volumeInstance.path, + 'deviceId': deviceId}) + try: + rc = self.provisionv3.delete_volume_from_pool( + self.conn, storageConfigService, volumeInstance.path, + volumeName) + + except Exception as e: + # If we cannot successfully delete the volume, then we want to + # return the volume to the default storage group, + # which should be the SG it previously belonged to. + storageGroupInstanceName = self.utils.find_storage_masking_group( + self.conn, controllerConfigurationService, storageGroupName) + + if sgFromVolInstanceName is not storageGroupInstanceName: + LOG.debug( + "Volume: %(volumeName)s was not previously part of " + " %(storageGroupInstanceName)s. " + "Returning to %(storageGroupName)s.", + {'volumeName': volumeName, + 'storageGroupInstanceName': storageGroupInstanceName, + 'storageGroupName': storageGroupName}) + + if storageGroupInstanceName is not None: + self.masking.add_volume_to_storage_group( + self.conn, controllerConfigurationService, + storageGroupInstanceName, volumeInstance, volumeName, + storageGroupName) + + LOG.error(_LE("Exception: %s."), e) + errorMessage = (_("Failed to delete volume %(volumeName)s.") % + {'volumeName': volumeName}) + LOG.error(errorMessage) + raise exception.VolumeBackendAPIException(data=errorMessage) + + return rc + + def _create_clone_v2(self, repServiceInstanceName, cloneVolume, + sourceVolume, sourceInstance, isSnapshot, + extraSpecs): + """Create a clone (v2). + + :param repServiceInstanceName: the replication service + :param cloneVolume: the clone volume object + :param sourceVolume: the source volume object + :param sourceInstance: the device ID of the volume + :param isSnapshot: check to see if it is a snapshot + :param fastPolicyName: the FAST policy name(if it exists) + :returns: rc + """ + # Check if the source volume contains any meta devices. + metaHeadInstanceName = self.utils.get_volume_meta_head( + self.conn, sourceInstance.path) + + if metaHeadInstanceName is None: # Simple volume. + return self._create_v2_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, None, isSnapshot, extraSpecs) + else: # Composite volume with meta device members. + # Check if the meta members capacity. + metaMemberInstanceNames = ( + self.utils.get_meta_members_of_composite_volume( + self.conn, metaHeadInstanceName)) + volumeCapacities = self.utils.get_meta_members_capacity_in_bit( + self.conn, metaMemberInstanceNames) + LOG.debug("Volume capacities: %(metasizes)s.", + {'metasizes': volumeCapacities}) + if len(set(volumeCapacities)) == 1: + LOG.debug("Meta volume all of the same size.") + return self._create_v2_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, None, isSnapshot, extraSpecs) + + LOG.debug("Meta volumes are of different sizes, " + "%d different sizes.", len(set(volumeCapacities))) + + baseTargetVolumeInstance = None + for volumeSizeInbits in volumeCapacities: + if baseTargetVolumeInstance is None: # Create base volume. + baseVolumeName = "TargetBaseVol" + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + rc, baseVolumeDict, storageSystemName = ( + self._create_composite_volume( + volume, extraSpecs, + baseVolumeName, volumeSizeInbits)) + baseTargetVolumeInstance = self.utils.find_volume_instance( + self.conn, baseVolumeDict, baseVolumeName) + LOG.debug("Base target volume %(targetVol)s created. " + "capacity in bits: %(capInBits)lu.", + {'capInBits': volumeSizeInbits, + 'targetVol': baseTargetVolumeInstance.path}) + else: # Create append volume + targetVolumeName = "MetaVol" + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + storageConfigService = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystemName)) + unboundVolumeInstance = ( + self._create_and_get_unbound_volume( + self.conn, storageConfigService, + baseTargetVolumeInstance.path, volumeSizeInbits)) + if unboundVolumeInstance is None: + exceptionMessage = (_( + "Error Creating unbound volume.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # Append the new unbound volume to the + # base target composite volume. + baseTargetVolumeInstance = self.utils.find_volume_instance( + self.conn, baseVolumeDict, baseVolumeName) + elementCompositionService = ( + self.utils.find_element_composition_service( + self.conn, storageSystemName)) + compositeType = self.utils.get_composite_type( + extraSpecs[COMPOSITETYPE]) + rc, modifiedVolumeDict = ( + self._modify_and_get_composite_volume_instance( + self.conn, + elementCompositionService, + baseTargetVolumeInstance, + unboundVolumeInstance.path, + targetVolumeName, + compositeType)) + if modifiedVolumeDict is None: + exceptionMessage = (_( + "Error appending volume %(volumename)s to " + "target base volume.") + % {'volumename': targetVolumeName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("Create V2 replica for meta members of different sizes.") + return self._create_v2_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, baseTargetVolumeInstance, isSnapshot, + extraSpecs) + + def _create_v2_replica_and_delete_clone_relationship( + self, repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, targetInstance, isSnapshot, extraSpecs): + """Create clone and delete relationship (v2). + + :param repServiceInstanceName: the replication service + :param cloneVolume: the clone volume object + :param sourceVolume: the source volume object + :param sourceInstance: the source volume instance + :param targetInstance: the target volume instance + :param isSnapshot: check to see if it is a snapshot + :param extraSpecs: additional information + :returns: rc + """ + sourceName = sourceVolume['name'] + cloneName = cloneVolume['name'] + rc, job = self.provision.create_element_replica( + self.conn, repServiceInstanceName, cloneName, sourceName, + sourceInstance, targetInstance) + cloneDict = self.provision.get_volume_dict_from_job( + self.conn, job['Job']) + + fastPolicyName = extraSpecs[FASTPOLICY] + if isSnapshot: + if fastPolicyName is not None: + storageSystemName = sourceInstance['SystemName'] + self._add_clone_to_default_storage_group( + fastPolicyName, storageSystemName, cloneDict, cloneName) + LOG.info(_LI("Snapshot creation %(cloneName)s completed. " + "Source Volume: %(sourceName)s."), + {'cloneName': cloneName, + 'sourceName': sourceName}) + + return rc, cloneDict + + cloneVolume['provider_location'] = six.text_type(cloneDict) + syncInstanceName, storageSystemName = ( + self._find_storage_sync_sv_sv(cloneVolume, sourceVolume)) + + # Remove the Clone relationship so it can be used as a regular lun. + # 8 - Detach operation. + rc, job = self.provision.delete_clone_relationship( + self.conn, repServiceInstanceName, syncInstanceName) + if fastPolicyName is not None: + self._add_clone_to_default_storage_group( + fastPolicyName, storageSystemName, cloneDict, cloneName) + + return rc, cloneDict + + def get_target_wwns_from_masking_view( + self, storageSystem, volume, connector): + """Find target WWNs via the masking view. + + :param storageSystem: the storage system name + :param volume: volume to be attached + :param connector: the connector dict + :returns: targetWwns, the target WWN list + """ + targetWwns = [] + mvInstanceName = self.get_masking_view_by_volume(volume, connector) + if mvInstanceName is not None: + targetWwns = self.masking.get_target_wwns( + self.conn, mvInstanceName) + LOG.info(_LI("Target wwns in masking view %(maskingView)s: " + "%(targetWwns)s."), + {'maskingView': mvInstanceName, + 'targetWwns': str(targetWwns)}) + return targetWwns + + def get_port_group_from_masking_view(self, maskingViewInstanceName): + return self.masking.get_port_group_from_masking_view( + self.conn, maskingViewInstanceName) + + def get_masking_view_by_volume(self, volume, connector): + """Given volume, retrieve the masking view instance name. + + :param volume: the volume + :param connector: the connector object + :returns maskingviewInstanceName + """ + LOG.debug("Finding Masking View for volume %(volume)s.", + {'volume': volume}) + volumeInstance = self._find_lun(volume) + return self.masking.get_masking_view_by_volume( + self.conn, volumeInstance, connector) + + def get_masking_views_by_port_group(self, portGroupInstanceName): + """Given port group, retrieve the masking view instance name. + + :param : the volume + :param mvInstanceName: masking view instance name + :returns: maksingViewInstanceNames + """ + LOG.debug("Finding Masking Views for port group %(pg)s.", + {'pg': portGroupInstanceName}) + return self.masking.get_masking_views_by_port_group( + self.conn, portGroupInstanceName) + + def _create_replica_v3( + self, repServiceInstanceName, cloneVolume, + sourceVolume, sourceInstance, isSnapshot): + """V3 specific function, create replica for source volume. + + This includes clone and snapshot. + + :param repServiceInstanceName: the replication service + :param cloneVolume: the clone volume object + :param sourceInstance: the device ID of the volume + :param isSnapshot: check to see if it is a snapshot + :returns: rc + """ + cloneName = cloneVolume['name'] + syncType = self.utils.get_num(8, '16') # Default syncType 8: clone. + + # Create target volume + extraSpecs = self._initial_setup(cloneVolume) + + numOfBlocks = sourceInstance['NumberOfBlocks'] + blockSize = sourceInstance['BlockSize'] + volumeSizeInbits = numOfBlocks * blockSize + + volume = {'size': + int(self.utils.convert_bits_to_gbs(volumeSizeInbits))} + _, volumeDict, storageSystemName = ( + self._create_v3_volume( + volume, extraSpecs, cloneName, volumeSizeInbits)) + targetInstance = self.utils.find_volume_instance( + self.conn, volumeDict, cloneName) + LOG.debug("Create replica target volume " + "source volume: %(sourceVol)s, " + "target volume: %(targetVol)s.", + {'sourceVol': sourceInstance.path, + 'targetVol': targetInstance.path}) + if isSnapshot: + # SyncType 7: snap, VG3R default snapshot is snapVx. + syncType = self.utils.get_num(7, '16') + + _, job = ( + self.provisionv3.create_element_replica( + self.conn, repServiceInstanceName, cloneName, syncType, + sourceInstance, targetInstance)) + cloneDict = self.provisionv3.get_volume_dict_from_job( + self.conn, job['Job']) + + cloneVolume['provider_location'] = six.text_type(cloneDict) + + syncInstanceName, _ = ( + self._find_storage_sync_sv_sv(cloneVolume, sourceVolume, True)) + + # Detach/dissolve the clone/snap relationship. + # 8 - Detach operation. + # 9 - Dissolve operation. + if isSnapshot: + # Operation 7: dissolve for snapVx. + operation = self.utils.get_num(9, '16') + else: + # Operation 8: detach for clone. + operation = self.utils.get_num(8, '16') + + rc, job = self.provisionv3.break_replication_relationship( + self.conn, repServiceInstanceName, syncInstanceName, + operation) + return rc, cloneDict + + def _delete_cg_and_members( + self, storageSystem, extraSpecs, cgName, modelUpdate, volumes): + """Helper function to delete a consistency group and its member volumes. + + :param storageSystem: storage system + :param repServiceInstanceName: the replication service + :param cgInstanceName: consistency group instance name + """ + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + + storageConfigservice = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystem)) + cgInstanceName = self._find_consistency_group( + replicationService, cgName) + if cgInstanceName is None: + exception_message = (_("Cannot find CG group %s.") % cgName) + raise exception.VolumeBackendAPIException( + data=exception_message) + memberInstanceNames = self._get_members_of_replication_group( + cgInstanceName) + + self.provision.delete_consistency_group( + self.conn, replicationService, cgInstanceName, cgName) + + if memberInstanceNames: + try: + controllerConfigurationService = ( + self.utils.find_controller_configuration_service( + self.conn, storageSystem)) + for memberInstanceName in memberInstanceNames: + self._remove_device_from_storage_group( + controllerConfigurationService, + memberInstanceName, 'Member Volume') + LOG.debug("Deleting CG members. CG: %(cg)s " + "%(numVols)lu member volumes: %(memVols)s.", + {'cg': cgInstanceName, + 'numVols': len(memberInstanceNames), + 'memVols': memberInstanceNames}) + if extraSpecs[ISV3]: + self.provisionv3.delete_volume_from_pool( + self.conn, storageConfigservice, + memberInstanceNames, None) + else: + self.provision.delete_volume_from_pool( + self.conn, storageConfigservice, + memberInstanceNames, None) + for volumeRef in volumes: + volumeRef['status'] = 'deleted' + except Exception: + for volumeRef in volumes: + volumeRef['status'] = 'error_deleting' + modelUpdate['status'] = 'error_deleting' + return modelUpdate, volumes diff --git a/cinder/volume/drivers/emc/emc_vmax_fast.py b/cinder/volume/drivers/emc/emc_vmax_fast.py index f6c839e1d..d0446acf0 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fast.py +++ b/cinder/volume/drivers/emc/emc_vmax_fast.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,8 +12,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import six - from cinder import exception from cinder.i18n import _, _LE, _LI, _LW from cinder.openstack.common import log as logging @@ -49,14 +47,12 @@ class EMCVMAXFast(object): isTieringPolicySupported = self.is_tiering_policy_enabled( conn, tierPolicyServiceInstanceName) if isTieringPolicySupported is None: - errorMessage = (_("Cannot determine whether " - "Tiering Policy is support on this array.")) - LOG.error(errorMessage) + LOG.error(_LE("Cannot determine whether " + "Tiering Policy is supported on this array.")) if isTieringPolicySupported is False: - errorMessage = (_("Tiering Policy is not " - "supported on this array.")) - LOG.error(errorMessage) + LOG.error(_LE("Tiering Policy is not " + "supported on this array.")) return isTieringPolicySupported def is_tiering_policy_enabled(self, conn, tierPolicyServiceInstanceName): @@ -80,7 +76,7 @@ class EMCVMAXFast(object): tierPolicyCapabilityInstance = conn.GetInstance( tierPolicyCapabilityInstanceName, LocalOnly=False) propertiesList = (tierPolicyCapabilityInstance - .properties.items()) # ['SupportsTieringPolicies'] + .properties.items()) for properties in propertiesList: if properties[0] == 'SupportsTieringPolicies': cimProperties = properties[1] @@ -88,9 +84,8 @@ class EMCVMAXFast(object): break if foundIsSupportsTieringPolicies is None: - exception_message = (_("Cannot determine if Tiering Policies " - "are supported")) - LOG.error(exception_message) + LOG.error(_LE("Cannot determine if Tiering Policies " + "are supported.")) return foundIsSupportsTieringPolicies @@ -115,9 +110,8 @@ class EMCVMAXFast(object): conn, controllerConfigService) if not self._check_if_fast_supported(conn, storageSystemInstanceName): - exceptionMessage = (_( - "FAST is not supported on this array ")) - LOG.error(exceptionMessage) + LOG.error(_LE( + "FAST is not supported on this array.")) raise assocStorageGroupInstanceName = ( @@ -129,23 +123,21 @@ class EMCVMAXFast(object): controllerConfigService, defaultSgGroupName)) if defaultStorageGroupInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Unable to find default storage group " - "for FAST policy : %(fastPolicyName)s ") - % {'fastPolicyName': fastPolicyName}) - LOG.error(exceptionMessage) + "for FAST policy : %(fastPolicyName)s."), + {'fastPolicyName': fastPolicyName}) raise if assocStorageGroupInstanceName == defaultStorageGroupInstanceName: foundDefaultStorageGroupInstanceName = ( assocStorageGroupInstanceName) else: - errorMessage = (_LW( + LOG.warn(_LW( "Volume: %(volumeName)s Does not belong " - "to storage storage group %(defaultSgGroupName)s. ") - % {'volumeName': volumeName, - 'defaultSgGroupName': defaultSgGroupName}) - LOG.warn(errorMessage) + "to storage storage group %(defaultSgGroupName)s."), + {'volumeName': volumeName, + 'defaultSgGroupName': defaultSgGroupName}) return foundDefaultStorageGroupInstanceName def add_volume_to_default_storage_group_for_fast_policy( @@ -170,11 +162,10 @@ class EMCVMAXFast(object): storageGroupInstanceName = self.utils.find_storage_masking_group( conn, controllerConfigService, defaultSgGroupName) if storageGroupInstanceName is None: - exceptionMessage = (_( - "Unable to create default storage group for" - " FAST policy : %(fastPolicyName)s ") - % {'fastPolicyName': fastPolicyName}) - LOG.error(exceptionMessage) + LOG.error(_LE( + "Unable to create default storage group for " + "FAST policy : %(fastPolicyName)s."), + {'fastPolicyName': fastPolicyName}) return failedRet self.provision.add_members_to_masking_group( @@ -207,11 +198,10 @@ class EMCVMAXFast(object): firstVolumeInstance = self._create_volume_for_default_volume_group( conn, controllerConfigService, volumeInstance.path) if firstVolumeInstance is None: - exceptionMessage = (_( - "Failed to create a first volume for storage" - " group : %(storageGroupName)s ") - % {'storageGroupName': storageGroupName}) - LOG.error(exceptionMessage) + LOG.error(_LE( + "Failed to create a first volume for storage " + "group : %(storageGroupName)s."), + {'storageGroupName': storageGroupName}) return failedRet defaultStorageGroupInstanceName = ( @@ -219,11 +209,10 @@ class EMCVMAXFast(object): conn, controllerConfigService, storageGroupName, firstVolumeInstance.path)) if defaultStorageGroupInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Failed to create default storage group for " - "FAST policy : %(fastPolicyName)s ") - % {'fastPolicyName': fastPolicyName}) - LOG.error(exceptionMessage) + "FAST policy : %(fastPolicyName)s."), + {'fastPolicyName': fastPolicyName}) return failedRet storageSystemInstanceName = ( @@ -235,11 +224,10 @@ class EMCVMAXFast(object): tierPolicyRuleInstanceName = self._get_service_level_tier_policy( conn, tierPolicyServiceInstanceName, fastPolicyName) if tierPolicyRuleInstanceName is None: - exceptionMessage = (_( + LOG.error(_LE( "Unable to get policy rule for fast policy: " - "%(fastPolicyName)s ") - % {'fastPolicyName': fastPolicyName}) - LOG.error(exceptionMessage) + "%(fastPolicyName)s."), + {'fastPolicyName': fastPolicyName}) return failedRet # Now associate it with a FAST policy. @@ -256,7 +244,7 @@ class EMCVMAXFast(object): Creates a small first volume for the default storage group for a fast policy. This is necessary because you cannot remove - the last volume from a storage group and this scenario is likely + the last volume from a storage group and this scenario is likely. :param conn: the connection information to the ecom server :param controllerConfigService: the controller configuration service @@ -274,15 +262,15 @@ class EMCVMAXFast(object): poolInstanceName = self.utils.get_assoc_pool_from_volume( conn, volumeInstanceName) if poolInstanceName is None: - exceptionMessage = (_("Unable to get associated pool of volume")) - LOG.error(exceptionMessage) + LOG.error(_LE("Unable to get associated pool of volume.")) return failedRet volumeName = 'vol1' volumeSize = '1' - volumeDict, rc = self.provision.create_volume_from_pool( - conn, storageConfigurationInstanceName, volumeName, - poolInstanceName, volumeSize) + volumeDict, _ = ( + self.provision.create_volume_from_pool( + conn, storageConfigurationInstanceName, volumeName, + poolInstanceName, volumeSize)) firstVolumeInstanceName = self.utils.find_volume_instance( conn, volumeDict, volumeName) return firstVolumeInstanceName @@ -314,7 +302,7 @@ class EMCVMAXFast(object): exceptionMessage = (_( "Error associating storage group : %(storageGroupName)s. " "To fast Policy: %(fastPolicyName)s with error " - "description: %(errordesc)s") + "description: %(errordesc)s.") % {'storageGroupName': storageGroupName, 'fastPolicyName': fastPolicyName, 'errordesc': errordesc}) @@ -329,7 +317,7 @@ class EMCVMAXFast(object): """Returns the existing tier policies for a storage system instance. Given the storage system instance name, get the existing tier - policies on that array + policies on that array. :param conn: the connection information to the ecom server :param tierPolicyServiceInstanceName: the policy service @@ -401,8 +389,8 @@ class EMCVMAXFast(object): if len(storageTierInstanceNames) == 0: storageTierInstanceNames = None - LOG.warn(_LW("Unable to get storage tiers " - "from tier policy rule.")) + LOG.warn(_LW( + "Unable to get storage tiers from tier policy rule.")) return storageTierInstanceNames @@ -436,7 +424,8 @@ class EMCVMAXFast(object): # Storage Group doesn't exist any more. foundStorageMaskingGroupInstanceName = None else: - foundStorageMaskingGroupInstanceName = instance.path + foundStorageMaskingGroupInstanceName = ( + storageMaskingGroupInstance.path) return foundStorageMaskingGroupInstanceName @@ -478,7 +467,7 @@ class EMCVMAXFast(object): """Adds a storage group to a tier policy and verifies success. Add a storage group to a tier policy rule and verify that it was - successful by getting the association + successful by getting the association. :param conn: the connection to the ecom server :param controllerConfigService: the controller config service @@ -497,18 +486,16 @@ class EMCVMAXFast(object): tierPolicyRuleInstanceName = self._get_service_level_tier_policy( conn, tierPolicyServiceInstanceName, fastPolicyName) if tierPolicyRuleInstanceName is None: - errorMessage = (_LE( - "Cannot find the fast policy %(fastPolicyName)s") - % {'fastPolicyName': fastPolicyName}) - - LOG.error(errorMessage) + LOG.error(_LE( + "Cannot find the fast policy %(fastPolicyName)s."), + {'fastPolicyName': fastPolicyName}) return failedRet else: LOG.debug( - "Adding storage group %(storageGroupInstanceName)s to" - " tier policy rule %(tierPolicyRuleInstanceName)s" - % {'storageGroupInstanceName': storageGroupInstanceName, - 'tierPolicyRuleInstanceName': tierPolicyRuleInstanceName}) + "Adding storage group %(storageGroupInstanceName)s to " + "tier policy rule %(tierPolicyRuleInstanceName)s.", + {'storageGroupInstanceName': storageGroupInstanceName, + 'tierPolicyRuleInstanceName': tierPolicyRuleInstanceName}) # Associate the new storage group with the existing fast policy. try: @@ -517,14 +504,12 @@ class EMCVMAXFast(object): storageGroupInstanceName, tierPolicyRuleInstanceName, storageGroupName, fastPolicyName) except Exception as ex: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) - errorMessage = (_LE( + LOG.error(_LE("Exception: %s"), ex) + LOG.error(_LE( "Failed to add storage group %(storageGroupInstanceName)s " - " to tier policy rule %(tierPolicyRuleInstanceName)s") - % {'storageGroupInstanceName': storageGroupInstanceName, - 'tierPolicyRuleInstanceName': - tierPolicyRuleInstanceName}) - LOG.error(errorMessage) + "to tier policy rule %(tierPolicyRuleInstanceName)s."), + {'storageGroupInstanceName': storageGroupInstanceName, + 'tierPolicyRuleInstanceName': tierPolicyRuleInstanceName}) return failedRet # Check that the storage group has been associated with with the @@ -535,8 +520,8 @@ class EMCVMAXFast(object): LOG.debug( "AssocTierPolicyInstanceName is " - "%(assocTierPolicyInstanceName)s " - % {'assocTierPolicyInstanceName': assocTierPolicyInstanceName}) + "%(assocTierPolicyInstanceName)s.", + {'assocTierPolicyInstanceName': assocTierPolicyInstanceName}) return assocTierPolicyInstanceName def get_associated_policy_from_storage_group( @@ -573,8 +558,8 @@ class EMCVMAXFast(object): associated with the storage group """ modificationType = '6' - LOG.debug("Invoking ModifyStorageTierPolicyRule" - " %s" % tierPolicyRuleInstanceName) + LOG.debug("Invoking ModifyStorageTierPolicyRule %s.", + tierPolicyRuleInstanceName) try: rc, job = conn.InvokeMethod( 'ModifyStorageTierPolicyRule', tierPolicyServiceInstanceName, @@ -585,14 +570,14 @@ class EMCVMAXFast(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: LOG.error(_LE("Error disassociating storage group from " - "policy: %s") % errordesc) + "policy: %s."), errordesc) else: - LOG.debug("Disassociated storage group from policy %s") + LOG.debug("Disassociated storage group from policy.") else: - LOG.debug("ModifyStorageTierPolicyRule completed") + LOG.debug("ModifyStorageTierPolicyRule completed.") except Exception as e: - LOG.info(_LI("Storage group not associated with the policy %s") - % six.text_type(e)) + LOG.info(_LI("Storage group not associated with the " + "policy. Exception is %s."), e) def get_pool_associated_to_policy( self, conn, fastPolicyName, arraySN, @@ -600,7 +585,7 @@ class EMCVMAXFast(object): """Given a FAST policy check that the pool is linked to the policy. If it's associated return the pool instance, if not return None. - First check if FAST is enabled on the array + First check if FAST is enabled on the array. :param conn: the ecom connection :param fastPolicyName: the fast policy name (String) @@ -615,7 +600,7 @@ class EMCVMAXFast(object): if not self._check_if_fast_supported(conn, storageSystemInstanceName): errorMessage = (_( - "FAST is not supported on this array ")) + "FAST is not supported on this array.")) LOG.error(errorMessage) exception.VolumeBackendAPIException(data=errorMessage) @@ -659,7 +644,7 @@ class EMCVMAXFast(object): isTieringPolicySupported = self.is_tiering_policy_enabled( conn, tierPolicyServiceInstanceName) except Exception as e: - LOG.error(_LE("Exception: %s") % six.text_type(e)) + LOG.error(_LE("Exception: %s."), e) return False return isTieringPolicySupported @@ -713,22 +698,19 @@ class EMCVMAXFast(object): # Check that pool hasn't suddenly been deleted. storagePoolInstance = self.utils.get_existing_instance( conn, poolInstanceName) - if storagePoolInstance is None: # Pool doesn't exist any more. break - total_capacity_gb += self.utils.convert_bits_to_gbs( storagePoolInstance['TotalManagedSpace']) allocated_capacity_gb += self.utils.convert_bits_to_gbs( storagePoolInstance['EMCSubscribedCapacity']) - LOG.debug( - "policyName:%(policyName)s, pool: %(poolInstanceName)s, " - "allocated_capacity_gb = %(allocated_capacity_gb)lu" - % {'policyName': policyName, - 'poolInstanceName': poolInstanceName, - 'allocated_capacity_gb': allocated_capacity_gb}) + "PolicyName:%(policyName)s, pool: %(poolInstanceName)s, " + "allocated_capacity_gb = %(allocated_capacity_gb)lu.", + {'policyName': policyName, + 'poolInstanceName': poolInstanceName, + 'allocated_capacity_gb': allocated_capacity_gb}) free_capacity_gb = total_capacity_gb - allocated_capacity_gb return (total_capacity_gb, free_capacity_gb) @@ -752,7 +734,7 @@ class EMCVMAXFast(object): controllerConfigService, defaultSgGroupName)) if defaultStorageGroupInstanceName is None: - # create it and associate it with the FAST policy in question + # Create it and associate it with the FAST policy in question. defaultStorageGroupInstanceName = ( self._create_default_storage_group(conn, controllerConfigService, @@ -798,13 +780,13 @@ class EMCVMAXFast(object): volumeInstanceName, ResultClass='CIM_DeviceMaskingGroup') if len(sgInstanceNames) == 0: - LOG.debug("volume %(vol)s is not in default sg." - % {'vol': volumeInstanceName}) + LOG.debug("volume %(vol)s is not in default sg.", + {'vol': volumeInstanceName}) return False else: for sgInstance in sgInstanceNames: if DEFAULT_SG_PREFIX in sgInstance['InstanceID']: - LOG.debug("volume %(vol)s already in default sg." - % {'vol': volumeInstanceName}) + LOG.debug("volume %(vol)s already in default sg.", + {'vol': volumeInstanceName}) return True return False diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 580dc8844..83113154e 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 EMC Corporation. +# Copyright (c) 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,7 +15,7 @@ import six from cinder import context -from cinder.i18n import _LI, _LW +from cinder.i18n import _LW from cinder.openstack.common import log as logging from cinder.volume import driver from cinder.volume.drivers.emc import emc_vmax_common @@ -32,9 +32,10 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 1.1.0 - Multiple pools and thick/thin provisioning, performance enhancement. 2.0.0 - Add driver requirement functions + 2.1.0 - Add consistency group functions """ - VERSION = "2.0.0" + VERSION = "2.1.0" def __init__(self, *args, **kwargs): @@ -163,8 +164,8 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 'target_wwn': target_wwns, 'initiator_target_map': init_targ_map}} - LOG.debug("Return FC data for zone addition: %(data)s." - % {'data': data}) + LOG.debug("Return FC data for zone addition: %(data)s.", + {'data': data}) return data @@ -180,11 +181,12 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): :returns: data - the target_wwns and initiator_target_map if the zone is to be removed, otherwise empty """ + data = {} loc = volume['provider_location'] name = eval(loc) storage_system = name['keybindings']['SystemName'] - LOG.info(_LI("Start FC detach process for volume: %(volume)s") - % {'volume': volume['name']}) + LOG.debug("Start FC detach process for volume: %(volume)s.", + {'volume': volume['name']}) mvInstanceName = self.common.get_masking_view_by_volume( volume, connector) @@ -195,15 +197,15 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): self.common.get_port_group_from_masking_view( mvInstanceName)) - LOG.info(_LI("Found port group: %(portGroup)s " - "in masking view %(maskingView)s"), - {'portGroup': portGroupInstanceName, - 'maskingView': mvInstanceName}) + LOG.debug("Found port group: %(portGroup)s " + "in masking view %(maskingView)s.", + {'portGroup': portGroupInstanceName, + 'maskingView': mvInstanceName}) self.common.terminate_connection(volume, connector) LOG.debug("Looking for masking views still associated with " - "Port Group %s", portGroupInstanceName) + "Port Group %s.", portGroupInstanceName) mvInstances = self.common.get_masking_views_by_port_group( portGroupInstanceName) if len(mvInstances) > 0: @@ -216,7 +218,6 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): data = {'driver_volume_type': 'fibre_channel', 'data': {'target_wwn': target_wwns, 'initiator_target_map': init_targ_map}} - LOG.debug("Return FC data for zone removal: %(data)s.", {'data': data}) else: @@ -228,7 +229,6 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): """Build the target_wwns and the initiator target map.""" target_wwns = [] init_targ_map = {} - initiator_wwns = connector['wwpns'] if self.zonemanager_lookup_service: @@ -298,3 +298,21 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): "returns: list """ return self.common.retype(ctxt, volume, new_type, diff, host) + + def create_consistencygroup(self, context, group): + """Creates a consistencygroup.""" + self.common.create_consistencygroup(context, group) + + def delete_consistencygroup(self, context, group): + """Deletes a consistency group.""" + volumes = self.db.volume_get_all_by_group(context, group['id']) + return self.common.delete_consistencygroup( + context, group, volumes) + + def create_cgsnapshot(self, context, cgsnapshot): + """Creates a cgsnapshot.""" + return self.common.create_cgsnapshot(context, cgsnapshot, self.db) + + def delete_cgsnapshot(self, context, cgsnapshot): + """Deletes a cgsnapshot.""" + return self.common.delete_cgsnapshot(context, cgsnapshot, self.db) diff --git a/cinder/volume/drivers/emc/emc_vmax_https.py b/cinder/volume/drivers/emc/emc_vmax_https.py new file mode 100644 index 000000000..fde410340 --- /dev/null +++ b/cinder/volume/drivers/emc/emc_vmax_https.py @@ -0,0 +1,343 @@ +# Copyright (c) 2012 - 2015 EMC Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# 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 +import six + +from cinder.i18n import _, _LI +from cinder.openstack.common import log as logging + +# Handle case where we are running in a monkey patched environment +if patcher.is_monkey_patched('socket'): + from eventlet.green.OpenSSL.SSL import GreenConnection as Connection +else: + raise ImportError + +try: + import pywbem + pywbemAvailable = True +except ImportError: + pywbemAvailable = False + + +LOG = logging.getLogger(__name__) + + +def to_bytes(s): + if isinstance(s, six.string_types): + return six.b(s) + else: + return s + + +def get_default_ca_certs(): + """Try to find out system path with ca certificates. This path is cached and + returned. If no path is found out, None is returned. + """ + if not hasattr(get_default_ca_certs, '_path'): + for path in ( + '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt', + '/etc/ssl/certs', + '/etc/ssl/certificates'): + if os.path.exists(path): + get_default_ca_certs._path = path + break + else: + get_default_ca_certs._path = None + return get_default_ca_certs._path + + +class OpenSSLConnectionDelegator(object): + """An OpenSSL.SSL.Connection delegator. + + Supplies an additional 'makefile' method which httplib requires + and is not present in OpenSSL.SSL.Connection. + Note: Since it is not possible to inherit from OpenSSL.SSL.Connection + a delegator must be used. + """ + def __init__(self, *args, **kwargs): + self.connection = Connection(*args, **kwargs) + + def __getattr__(self, name): + return getattr(self.connection, name) + + def makefile(self, *args, **kwargs): + return socket._fileobject(self.connection, *args, **kwargs) + + +class HTTPSConnection(httplib.HTTPSConnection): + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, ca_certs=None, no_verification=False): + if not pywbemAvailable: + LOG.info(_LI( + 'Module PyWBEM not installed. ' + 'Install PyWBEM using the python-pywbem package.')) + if six.PY3: + excp_lst = (TypeError, ssl.SSLError) + else: + excp_lst = () + try: + httplib.HTTPSConnection.__init__(self, host, port, + key_file=key_file, + cert_file=cert_file) + + self.key_file = None if key_file is None else key_file + self.cert_file = None if cert_file is None else cert_file + self.insecure = no_verification + self.ca_certs = None if ca_certs is None else str(ca_certs) + self.set_context() + # ssl exceptions are reported in various form in Python 3 + # so to be compatible, we report the same kind as under + # Python2 + except excp_lst as e: + raise pywbem.cim_http.Error(six.text_type(e)) + + @staticmethod + def host_matches_cert(host, x509): + """Verify that the certificate matches host. + + Verify that the x509 certificate we have received + from 'host' correctly identifies the server we are + connecting to, ie that the certificate's Common Name + or a Subject Alternative Name matches 'host'. + """ + def check_match(name): + # Directly match the name. + if name == host: + return True + + # Support single wildcard matching. + if name.startswith('*.') and host.find('.') > 0: + if name[2:] == host.split('.', 1)[1]: + return True + + common_name = x509.get_subject().commonName + # First see if we can match the CN. + if check_match(common_name): + return True + # Also try Subject Alternative Names for a match. + san_list = None + for i in range(x509.get_extension_count()): + ext = x509.get_extension(i) + if ext.get_short_name() == b'subjectAltName': + san_list = six.text_type(ext) + for san in ''.join(san_list.split()).split(','): + if san.startswith('DNS:'): + if check_match(san.split(':', 1)[1]): + return True + + # Server certificate does not match host. + msg = (_("Host %(host)s does not match x509 certificate contents: " + "CommonName %(commonName)s.") + % {'host': host, + 'commonName': common_name}) + + if san_list is not None: + msg = (_("%(message)s, subjectAltName: %(sanList)s.") + % {'message': msg, + 'sanList': san_list}) + raise pywbem.cim_http.AuthError(msg) + + def verify_callback(self, connection, x509, errnum, + depth, preverify_ok): + if x509.has_expired(): + msg = msg = (_("SSL Certificate expired on %s.") + % x509.get_notAfter()) + raise pywbem.cim_http.AuthError(msg) + + if depth == 0 and preverify_ok: + # We verify that the host matches against the last + # certificate in the chain. + return self.host_matches_cert(self.host, x509) + else: + # Pass through OpenSSL's default result. + return preverify_ok + + def set_context(self): + """Set up the OpenSSL context.""" + self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + + if self.insecure is not True: + self.context.set_verify(OpenSSL.SSL.VERIFY_PEER, + self.verify_callback) + else: + self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, + lambda *args: True) + + if self.cert_file: + try: + self.context.use_certificate_file(self.cert_file) + except Exception as e: + msg = (_("Unable to load cert from %(cert)s %(e)s.") + % {'cert': self.cert_file, + 'e': e}) + raise pywbem.cim_http.AuthError(msg) + if self.key_file is None: + # We support having key and cert in same file. + try: + self.context.use_privatekey_file(self.cert_file) + except Exception as e: + msg = (_("No key file specified and unable to load key " + "from %(cert)s %(e)s.") + % {'cert': self.cert_file, + 'e': e}) + raise pywbem.cim_http.AuthError(msg) + + if self.key_file: + try: + self.context.use_privatekey_file(self.key_file) + except Exception as e: + msg = (_("Unable to load key from %(cert)s %(e)s.") + % {'cert': self.cert_file, + 'e': e}) + raise pywbem.cim_http.AuthError(msg) + + if self.ca_certs: + try: + self.context.load_verify_locations(to_bytes(self.ca_certs)) + except Exception as e: + msg = (_("Unable to load CA from %(cert)s %(e)s.") + % {'cert': self.cert_file, + 'e': e}) + raise pywbem.cim_http.AuthError(msg) + else: + self.context.set_default_verify_paths() + + def connect(self): + result = socket.getaddrinfo(self.host, self.port, 0, + socket.SOCK_STREAM) + if result: + socket_family = result[0][0] + if socket_family == socket.AF_INET6: + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + else: + # If due to some reason the address lookup fails - we still + # connect to IPv4 socket. This retains the older behavior. + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if self.timeout is not None: + # '0' microseconds + sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, + struct.pack('LL', 0, 0)) + self.sock = OpenSSLConnectionDelegator(self.context, sock) + self.sock.connect((self.host, self.port)) + + +def wbem_request(url, data, creds, headers=None, debug=0, x509=None, + verify_callback=None, ca_certs=None, + no_verification=False): + """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 + dictionary containing the location of the SSL certificate and key + files. + """ + + if headers is None: + headers = [] + + host, port, use_ssl = pywbem.cim_http.parse_url(url) + key_file = None + cert_file = None + if use_ssl and x509 is not None: + cert_file = x509.get('cert_file') + key_file = x509.get('key_file') + + numTries = 0 + localAuthHeader = None + tryLimit = 5 + + if isinstance(data, unicode): + data = data.encode('utf-8') + data = '\n' + data + + if not no_verification and ca_certs is None: + ca_certs = get_default_ca_certs() + elif no_verification: + ca_certs = None + + if use_ssl: + h = HTTPSConnection( + host, + port=port, + key_file=key_file, + cert_file=cert_file, + ca_certs=ca_certs, + no_verification=no_verification) + + locallogin = None + while numTries < tryLimit: + numTries = numTries + 1 + + h.putrequest('POST', '/cimom') + h.putheader('Content-type', 'application/xml; charset="utf-8"') + h.putheader('Content-length', len(data)) + if localAuthHeader is not None: + h.putheader(*localAuthHeader) + elif creds is not None: + h.putheader('Authorization', 'Basic %s' % + base64.encodestring('%s:%s' % (creds[0], creds[1])) + .replace('\n', '')) + elif locallogin is not None: + h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin) + + for hdr in headers: + if isinstance(hdr, unicode): + 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])) + + try: + h.endheaders() + try: + h.send(data) + except socket.error as arg: + if arg[0] != 104 and arg[0] != 32: + raise + + response = h.getresponse() + body = response.read() + + if response.status != 200: + raise pywbem.cim_http.Error('HTTP error') + + except httplib.BadStatusLine as arg: + msg = (_("Bad Status line returned: %(arg)s.") + % {'arg': arg}) + raise pywbem.cim_http.Error(msg) + except socket.error as arg: + msg = (_("Socket error:: %(arg)s.") + % {'arg': arg}) + raise pywbem.cim_http.Error(msg) + except socket.sslerror as arg: + msg = (_("SSL error: %(arg)s.") + % {'arg': arg}) + raise pywbem.cim_http.Error(msg) + + break + + return body diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 58eed9014..6decf8ba1 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -16,17 +16,22 @@ ISCSI Drivers for EMC VMAX arrays based on SMI-S. """ +import os + import six from cinder import context from cinder import exception -from cinder.i18n import _, _LI +from cinder.i18n import _, _LE, _LI from cinder.openstack.common import log as logging from cinder.volume import driver from cinder.volume.drivers.emc import emc_vmax_common + LOG = logging.getLogger(__name__) +CINDER_CONF = '/etc/cinder/cinder.conf' + class EMCVMAXISCSIDriver(driver.ISCSIDriver): """EMC ISCSI Drivers for VMAX using SMI-S. @@ -36,9 +41,10 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 1.1.0 - Multiple pools and thick/thin provisioning, performance enhancement. 2.0.0 - Add driver requirement functions + 2.1.0 - Add consistency group functions """ - VERSION = "2.0.0" + VERSION = "2.1.0" def __init__(self, *args, **kwargs): @@ -48,9 +54,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): configuration=self.configuration) def check_for_setup_error(self): - if not self.configuration.iscsi_ip_address: - raise exception.InvalidInput( - reason=_('iscsi_ip_address is not set.')) + pass def create_volume(self, volume): """Creates a EMC(VMAX/VNX) volume.""" @@ -140,20 +144,23 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): } } """ - self.common.initialize_connection(volume, connector) + self.common.initialize_connection( + volume, connector) iscsi_properties = self.smis_get_iscsi_properties( volume, connector) - LOG.info(_LI("Leaving initialize_connection: %s") % (iscsi_properties)) + LOG.info(_LI("Leaving initialize_connection: %s"), (iscsi_properties)) return { 'driver_volume_type': 'iscsi', 'data': iscsi_properties } def smis_do_iscsi_discovery(self, volume): - LOG.info(_LI("ISCSI provider_location not stored, using discovery.")) + if not self._check_for_iscsi_ip_address(): + LOG.error(_LE( + "You must set your iscsi_ip_address in cinder.conf.")) (out, _err) = self._execute('iscsiadm', '-m', 'discovery', '-t', 'sendtargets', '-p', @@ -161,8 +168,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): run_as_root=True) LOG.info(_LI( - "smis_do_iscsi_discovery is: %(out)s") - % {'out': out}) + "smis_do_iscsi_discovery is: %(out)s."), + {'out': out}) targets = [] for target in out.splitlines(): targets.append(target) @@ -190,24 +197,24 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): location = self.smis_do_iscsi_discovery(volume) if not location: raise exception.InvalidVolume(_("Could not find iSCSI export " - " for volume %(volumeName)s") + " for volume %(volumeName)s.") % {'volumeName': volume['name']}) - LOG.debug("ISCSI Discovery: Found %s" % (location)) + LOG.debug("ISCSI Discovery: Found %s", (location)) properties['target_discovered'] = True device_info = self.common.find_device_number(volume) if device_info is None or device_info['hostlunid'] is None: exception_message = (_("Cannot find device number for volume " - "%(volumeName)s") + "%(volumeName)s.") % {'volumeName': volume['name']}) raise exception.VolumeBackendAPIException(data=exception_message) device_number = device_info['hostlunid'] LOG.info(_LI( - "location is: %(location)s") % {'location': location}) + "location is: %(location)s"), {'location': location}) for loc in location: results = loc.split(" ") @@ -218,15 +225,15 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): properties['volume_id'] = volume['id'] - LOG.info(_LI("ISCSI properties: %(properties)s") - % {'properties': properties}) - LOG.info(_LI("ISCSI volume is: %(volume)s") - % {'volume': volume}) + LOG.info(_LI( + "ISCSI properties: %(properties)s"), {'properties': properties}) + LOG.info(_LI( + "ISCSI volume is: %(volume)s"), {'volume': volume}) if 'provider_auth' in volume: auth = volume['provider_auth'] - LOG.info(_LI("AUTH properties: %(authProps)s") - % {'authProps': auth}) + LOG.info(_LI( + "AUTH properties: %(authProps)s"), {'authProps': auth}) if auth is not None: (auth_method, auth_username, auth_secret) = auth.split() @@ -235,7 +242,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): properties['auth_username'] = auth_username properties['auth_password'] = auth_secret - LOG.info(_LI("AUTH properties: %s") % (properties)) + LOG.info(_LI("AUTH properties: %s."), (properties)) return properties @@ -290,3 +297,32 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): {} """ return self.common.retype(ctxt, volume, new_type, diff, host) + + def create_consistencygroup(self, context, group): + """Creates a consistencygroup.""" + self.common.create_consistencygroup(context, group) + + def delete_consistencygroup(self, context, group): + """Deletes a consistency group.""" + volumes = self.db.volume_get_all_by_group(context, group['id']) + return self.common.delete_consistencygroup( + context, group, volumes) + + def create_cgsnapshot(self, context, cgsnapshot): + """Creates a cgsnapshot.""" + return self.common.create_cgsnapshot(context, cgsnapshot, self.db) + + def delete_cgsnapshot(self, context, cgsnapshot): + """Deletes a cgsnapshot.""" + return self.common.delete_cgsnapshot(context, cgsnapshot, self.db) + + def _check_for_iscsi_ip_address(self): + """Check to see if iscsi_ip_address is set in cinder.conf + + :returns: True/False + """ + bExists = os.path.exists(CINDER_CONF) + if bExists: + if 'iscsi_ip_address' in open(CINDER_CONF).read(): + return True + return False diff --git a/cinder/volume/drivers/emc/emc_vmax_masking.py b/cinder/volume/drivers/emc/emc_vmax_masking.py index d569fe065..41f3702a2 100644 --- a/cinder/volume/drivers/emc/emc_vmax_masking.py +++ b/cinder/volume/drivers/emc/emc_vmax_masking.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,6 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import six from cinder import exception @@ -19,6 +20,7 @@ from cinder.i18n import _, _LE, _LI, _LW from cinder.openstack.common import log as logging from cinder.volume.drivers.emc import emc_vmax_fast 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 LOG = logging.getLogger(__name__) @@ -45,12 +47,15 @@ class EMCVMAXMasking(object): self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl) self.fast = emc_vmax_fast.EMCVMAXFast(prtcl) self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl) + self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl) def get_or_create_masking_view_and_map_lun(self, conn, maskingViewDict): - """Get or Create a masking view. + """Get or Create a masking view and add a volume to the storage group. Given a masking view tuple either get or create a masking view and add the volume to the associated storage group. + If it is a live migration operation then we do not need to remove + the volume from any storage group (default or otherwise). :param conn: the connection to ecom :para maskingViewDict: the masking view tuple @@ -62,22 +67,45 @@ class EMCVMAXMasking(object): volumeInstance = maskingViewDict['volumeInstance'] maskingViewName = maskingViewDict['maskingViewName'] volumeName = maskingViewDict['volumeName'] + isV3 = maskingViewDict['isV3'] isLiveMigration = maskingViewDict['isLiveMigration'] - fastPolicyName = maskingViewDict['fastPolicy'] defaultStorageGroupInstanceName = None - + fastPolicyName = None + assocStorageGroupName = None if isLiveMigration is False: - # We need a rollback scenario for FAST. - # We must make sure that volume is returned to default storage - # group if anything goes wrong. - fastPolicyName = maskingViewDict['fastPolicy'] - # If FAST is enabled remove the volume from the default SG. - if fastPolicyName is not None: - defaultStorageGroupInstanceName = ( - self._get_and_remove_from_storage_group_v2( - conn, controllerConfigService, - volumeInstance.path, - volumeName, fastPolicyName)) + if isV3: + assocStorageGroupInstanceName = ( + self.utils.get_storage_group_from_volume( + conn, volumeInstance.path)) + instance = conn.GetInstance( + assocStorageGroupInstanceName, LocalOnly=False) + assocStorageGroupName = instance['ElementName'] + defaultSgGroupName = self.utils.get_v3_storage_group_name( + maskingViewDict['pool'], + maskingViewDict['slo'], + maskingViewDict['workload']) + + if assocStorageGroupName != defaultSgGroupName: + LOG.warn(_LW( + "Volume: %(volumeName)s Does not belong " + "to storage storage group %(defaultSgGroupName)s."), + {'volumeName': volumeName, + 'defaultSgGroupName': defaultSgGroupName}) + defaultStorageGroupInstanceName = assocStorageGroupInstanceName + + self._get_and_remove_from_storage_group_v3( + conn, controllerConfigService, volumeInstance.path, + volumeName, maskingViewDict, + defaultStorageGroupInstanceName) + else: + fastPolicyName = maskingViewDict['fastPolicy'] + # If FAST is enabled remove the volume from the default SG. + if fastPolicyName is not None: + defaultStorageGroupInstanceName = ( + self._get_and_remove_from_storage_group_v2( + conn, controllerConfigService, + volumeInstance.path, + volumeName, fastPolicyName)) # Validate new or existing masking view. # Return the storage group so we can add the volume to it. @@ -103,6 +131,7 @@ class EMCVMAXMasking(object): rollbackDict['volumeInstance'] = volumeInstance rollbackDict['volumeName'] = volumeName rollbackDict['fastPolicyName'] = fastPolicyName + rollbackDict['isV3'] = isV3 if errorMessage: # Rollback code if we cannot complete any of the steps above @@ -111,13 +140,19 @@ class EMCVMAXMasking(object): if (fastPolicyName is not None): # If the errorMessage was returned before the volume # was removed from the default storage group no action. - self.check_if_rollback_action_for_masking_required( + self._check_if_rollback_action_for_masking_required( + conn, rollbackDict) + if isV3: + rollbackDict['sgGroupName'] = assocStorageGroupName + rollbackDict['storageSystemName'] = ( + maskingViewDict['storageSystemName']) + self._check_if_rollback_action_for_masking_required( conn, rollbackDict) exceptionMessage = (_( "Failed to get, create or add volume %(volumeName)s " "to masking view %(maskingViewName)s. " - "The error message received was %(errorMessage)s. ") + "The error message received was %(errorMessage)s.") % {'maskingViewName': maskingViewName, 'volumeName': volumeName, 'errorMessage': errorMessage}) @@ -190,7 +225,7 @@ class EMCVMAXMasking(object): return None, storageGroupInstanceName, errorMessage # Only after the components of the MV have been validated, - # add the volume to the storage group and re-check that it + # add the volume to the storage group and recheck that it # has been successfully added. This is necessary before # creating a new masking view. errorMessage = self._check_adding_volume_to_storage_group( @@ -284,29 +319,12 @@ class EMCVMAXMasking(object): # This may be used in exception hence _ instead of _LE. msg = (_( "Cannot get storage group: %(sgGroupName)s " - "from masking view %(maskingViewInstanceName)s.") % + "from masking view %(maskingViewInstanceName)s. ") % {'sgGroupName': sgGroupName, 'maskingViewInstanceName': maskingViewInstanceName}) LOG.error(msg) return sgFromMvInstanceName, msg - def _get_storage_group_from_masking_view_instance( - self, conn, maskingViewInstance): - """Gets the Device Masking Group from masking view instance. - - :param conn: the connection to the ecom server - :param maskingViewInstance - :returns: instance name foundStorageGroupInstanceName - """ - foundStorageGroupInstanceName = None - groups = conn.AssociatorNames( - maskingViewInstance, - ResultClass='CIM_DeviceMaskingGroup') - if groups[0] > 0: - foundStorageGroupInstanceName = groups[0] - - return foundStorageGroupInstanceName - def _check_port_group(self, conn, controllerConfigService, pgGroupName): """Check that you can either get or create a port group. @@ -322,7 +340,7 @@ class EMCVMAXMasking(object): if portGroupInstanceName is None: # This may be used in exception hence _ instead of _LE. msg = (_( - "Cannot get port group: %(pgGroupName)s.") % + "Cannot get port group: %(pgGroupName)s. ") % {'pgGroupName': pgGroupName}) LOG.error(msg) @@ -331,7 +349,7 @@ class EMCVMAXMasking(object): def _check_initiator_group( self, conn, controllerConfigService, igGroupName, connector, storageSystemName): - """Check that initiator group can be either be got or created. + """Check that initiator group can be either retrieved or created. :param conn - the ecom connection :param controllerConfigService - controller configuration service @@ -349,7 +367,7 @@ class EMCVMAXMasking(object): # This may be used in exception hence _ instead of _LE. msg = (_( "Cannot get or create initiator group: " - "%(igGroupName)s.") % + "%(igGroupName)s. ") % {'igGroupName': igGroupName}) LOG.error(msg) @@ -377,7 +395,7 @@ class EMCVMAXMasking(object): # This may be used in exception hence _ instead of _LE. msg = (_( "Unable to verify initiator group: %(igGroupName)s " - "in masking view %(maskingViewName)s.") % + "in masking view %(maskingViewName)s. ") % {'igGroupName': igGroupName, 'maskingViewName': maskingViewName}) LOG.error(msg) @@ -405,7 +423,7 @@ class EMCVMAXMasking(object): if maskingViewInstanceName is None: # This may be used in exception hence _ instead of _LE. msg = (_( - "Cannot create masking view: %(maskingViewName)s.") % + "Cannot create masking view: %(maskingViewName)s. ") % {'maskingViewName': maskingViewName}) LOG.error(msg) @@ -430,7 +448,7 @@ class EMCVMAXMasking(object): volumeInstance): LOG.warn(_LW( "Volume: %(volumeName)s is already part " - "of storage group %(sgGroupName)s "), + "of storage group %(sgGroupName)s."), {'volumeName': volumeName, 'sgGroupName': sgGroupName}) else: @@ -444,10 +462,15 @@ class EMCVMAXMasking(object): # This may be used in exception hence _ instead of _LE. msg = (_( "Volume: %(volumeName)s was not added " - "to storage group %(sgGroupName)s.") % + "to storage group %(sgGroupName)s. ") % {'volumeName': volumeName, 'sgGroupName': sgGroupName}) LOG.error(msg) + else: + LOG.debug("Successfully added %(volumeName)s to " + "%(sgGroupName)s.", + {'volumeName': volumeName, + 'sgGroupName': sgGroupName}) return msg @@ -468,7 +491,7 @@ class EMCVMAXMasking(object): if defaultStorageGroupInstanceName is None: exceptionMessage = (_( "Cannot get the default storage group for FAST policy: " - "%(fastPolicyName)s. ") + "%(fastPolicyName)s.") % {'fastPolicyName': fastPolicyName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( @@ -481,7 +504,52 @@ class EMCVMAXMasking(object): if retStorageGroupInstanceName is None: exceptionMessage = (_( "Failed to remove volume %(volumeName)s from default SG: " - "%(volumeName)s. ") + "%(volumeName)s.") + % {'volumeName': volumeName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + return defaultStorageGroupInstanceName + + def _get_and_remove_from_storage_group_v3( + self, conn, controllerConfigService, volumeInstanceName, + volumeName, maskingViewDict, storageGroupInstanceName): + """Get the storage group and remove volume from it. + + :param controllerConfigService - controller configuration service + :param volumeInstanceName - volume instance name + :param volumeName - volume name + :param fastPolicyName - fast name + """ + + assocVolumeInstanceNames = self.get_devices_from_storage_group( + conn, storageGroupInstanceName) + LOG.debug( + "There are %(length)lu associated with the default storage group " + "before removing volume %(volumeName)s.", + {'length': len(assocVolumeInstanceNames), + 'volumeName': volumeName}) + + self.provision.remove_device_from_storage_group( + conn, controllerConfigService, storageGroupInstanceName, + volumeInstanceName, volumeName) + + assocVolumeInstanceNames = self.get_devices_from_storage_group( + conn, storageGroupInstanceName) + LOG.debug( + "There are %(length)lu associated with the default storage group " + "after removing volume %(volumeName)s.", + {'length': len(assocVolumeInstanceNames), + 'volumeName': volumeName}) + + # Required for unit tests. + emptyStorageGroupInstanceName = ( + self._wrap_get_storage_group_from_volume(conn, volumeInstanceName)) + + if emptyStorageGroupInstanceName is not None: + exceptionMessage = (_( + "Failed to remove volume %(volumeName)s from default SG: " + "%(volumeName)s.") % {'volumeName': volumeName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( @@ -503,27 +571,21 @@ class EMCVMAXMasking(object): self.utils.get_storage_group_from_volume( conn, volumeInstance.path)) - storageGroupInstance = conn.GetInstance( - storageGroupInstanceName, LocalOnly=False) - - LOG.debug( - "The existing storage group instance element name is: " - "%(existingElement)s. " - % {'existingElement': storageGroupInstance['ElementName']}) - if foundStorageGroupInstanceName is not None: + storageGroupInstance = conn.GetInstance( + storageGroupInstanceName, LocalOnly=False) + LOG.debug( + "The existing storage group instance element name is: " + "%(existingElement)s.", + {'existingElement': storageGroupInstance['ElementName']}) foundStorageGroupInstance = conn.GetInstance( foundStorageGroupInstanceName, LocalOnly=False) LOG.debug( "The found storage group instance element name is: " - "%(foundElement)s. " - % {'foundElement': foundStorageGroupInstance['ElementName']}) + "%(foundElement)s.", + {'foundElement': foundStorageGroupInstance['ElementName']}) if (foundStorageGroupInstance['ElementName'] == ( storageGroupInstance['ElementName'])): - LOG.warn(_LW( - "The volume is already part of storage group: " - "%(storageGroupInstanceName)s. ") - % {'storageGroupInstanceName': storageGroupInstanceName}) return True return False @@ -537,6 +599,7 @@ class EMCVMAXMasking(object): :returns: foundMaskingViewInstanceName masking view instance name """ foundMaskingViewInstanceName = None + storageSystemInstanceName = self.utils.find_storageSystem( conn, storageSystemName) maskingViewInstances = conn.Associators( @@ -560,7 +623,7 @@ class EMCVMAXMasking(object): {'maskingViewName': maskingViewName}) else: LOG.info(_LI( - "Found existing masking view: %(maskingViewName)s "), + "Found existing masking view: %(maskingViewName)s."), {'maskingViewName': maskingViewName}) return foundMaskingViewInstanceName @@ -575,44 +638,61 @@ class EMCVMAXMasking(object): group that will be part of the masking view. :param conn: connection the ecom server - :param maskingViewDict: masking view dictionary + :param controllerConfigService: the controller configuration service + :param storageGroupName: the proposed group name (String) + :param volumeInstance: useful information on the volume + :param fastPolicyName: the fast policy name (String) can be None + :param volumeName: the volume name (String) + :param storageSystemName: the storage system name (String) :param defaultStorageGroupInstanceName: the default storage group instance name (Can be None) :returns: foundStorageGroupInstanceName the instance Name of the storage group """ failedRet = None - foundStorageGroupInstanceName = ( - self.provision.create_and_get_storage_group( - conn, maskingViewDict['controllerConfigService'], - maskingViewDict['sgGroupName'], - maskingViewDict['volumeInstance'].path)) + controllerConfigService = maskingViewDict['controllerConfigService'] + storageGroupName = maskingViewDict['sgGroupName'] + isV3 = maskingViewDict['isV3'] + + if isV3: + workload = maskingViewDict['workload'] + pool = maskingViewDict['pool'] + slo = maskingViewDict['slo'] + foundStorageGroupInstanceName = ( + self.provisionv3.create_storage_group_v3( + conn, controllerConfigService, storageGroupName, + pool, slo, workload)) + else: + fastPolicyName = maskingViewDict['fastPolicy'] + volumeInstance = maskingViewDict['volumeInstance'] + foundStorageGroupInstanceName = ( + self.provision.create_and_get_storage_group( + conn, controllerConfigService, storageGroupName, + volumeInstance.path)) + if (fastPolicyName is not None and + defaultStorageGroupInstanceName is not None): + assocTierPolicyInstanceName = ( + self.fast.add_storage_group_and_verify_tier_policy_assoc( + conn, controllerConfigService, + foundStorageGroupInstanceName, + storageGroupName, fastPolicyName)) + if assocTierPolicyInstanceName is None: + LOG.error(_LE( + "Cannot add and verify tier policy association for " + "storage group : %(storageGroupName)s to " + "FAST policy : %(fastPolicyName)s."), + {'storageGroupName': storageGroupName, + 'fastPolicyName': fastPolicyName}) + return failedRet if foundStorageGroupInstanceName is None: LOG.error(_LE( "Cannot get storage Group from job : %(storageGroupName)s."), - {'storageGroupName': maskingViewDict['sgGroupName']}) + {'storageGroupName': storageGroupName}) return failedRet else: LOG.info(_LI( - "Created new storage group: %(storageGroupName)s "), - {'storageGroupName': maskingViewDict['sgGroupName']}) - - if (maskingViewDict['fastPolicy'] is not None and - defaultStorageGroupInstanceName is not None): - assocTierPolicyInstanceName = ( - self.fast.add_storage_group_and_verify_tier_policy_assoc( - conn, maskingViewDict['controllerConfigService'], - foundStorageGroupInstanceName, - maskingViewDict['sgGroupName'], - maskingViewDict['fastPolicy'])) - if assocTierPolicyInstanceName is None: - LOG.error(_LE( - "Cannot add and verify tier policy association for storage" - " group : %(storageGroupName)s to FAST policy : " - "%(fastPolicyName)s. "), - {'storageGroupName': maskingViewDict['sgGroupName'], - 'fastPolicyName': maskingViewDict['fastPolicy']}) - return failedRet + "Created new storage group: %(storageGroupName)s."), + {'storageGroupName': storageGroupName}) return foundStorageGroupInstanceName @@ -641,9 +721,9 @@ class EMCVMAXMasking(object): if foundPortGroupInstanceName is None: LOG.error(_LE( - "Could not find port group : %(portGroupName)s. Check that the" - " EMC configuration file has the correct port group name. ") - % {'portGroupName': portGroupName}) + "Could not find port group : %(portGroupName)s. Check that " + "the EMC configuration file has the correct port group name."), + {'portGroupName': portGroupName}) return foundPortGroupInstanceName @@ -652,13 +732,12 @@ class EMCVMAXMasking(object): connector, storageSystemName): """Attempt to create a initiatorGroup. - If one already exists with the same Initiator/wwns then get it - + If one already exists with the same Initiator/wwns then get it. Check to see if an initiatorGroup already exists, that matches the - connector information + connector information. NOTE: An initiator/wwn can only belong to one initiatorGroup. If we were to attempt to create one with an initiator/wwn that - is already belong to another initiatorGroup, it would fail + is already belong to another initiatorGroup, it would fail. :param conn: connection to the ecom server :param controllerConfigService: the controller config Servicer @@ -669,8 +748,8 @@ class EMCVMAXMasking(object): """ failedRet = None initiatorNames = self._find_initiator_names(conn, connector) - LOG.debug("The initiator name(s) are: %(initiatorNames)s " - % {'initiatorNames': initiatorNames}) + LOG.debug("The initiator name(s) are: %(initiatorNames)s.", + {'initiatorNames': initiatorNames}) foundInitiatorGroupInstanceName = self._find_initiator_masking_group( conn, controllerConfigService, initiatorNames) @@ -679,28 +758,29 @@ class EMCVMAXMasking(object): # info create a new initiatorGroup. if foundInitiatorGroupInstanceName is None: # Check that our connector information matches the - # hardwareId(s) on the symm. + # hardwareId(s) on the vmax. storageHardwareIDInstanceNames = ( self._get_storage_hardware_id_instance_names( conn, initiatorNames, storageSystemName)) if not storageHardwareIDInstanceNames: LOG.error(_LE( "Initiator Name(s) %(initiatorNames)s are not on array " - "%(storageSystemName)s ") - % {'initiatorNames': initiatorNames, - 'storageSystemName': storageSystemName}) + "%(storageSystemName)s."), + {'initiatorNames': initiatorNames, + 'storageSystemName': storageSystemName}) return failedRet foundInitiatorGroupInstanceName = self._create_initiator_Group( conn, controllerConfigService, igGroupName, storageHardwareIDInstanceNames) - LOG.info(_LI("Created new initiator group name: %(igGroupName)s ") - % {'igGroupName': igGroupName}) + LOG.info(_LI( + "Created new initiator group name: %(igGroupName)s."), + {'igGroupName': igGroupName}) else: - LOG.info(_LI("Using existing initiator " - "group name: %(igGroupName)s ") - % {'igGroupName': igGroupName}) + LOG.info(_LI( + "Using existing initiator group name: %(igGroupName)s."), + {'igGroupName': igGroupName}) return foundInitiatorGroupInstanceName @@ -715,19 +795,26 @@ class EMCVMAXMasking(object): name = 'initiator name' if (self.protocol.lower() == ISCSI and connector['initiator']): foundinitiatornames.append(connector['initiator']) - elif (self.protocol.lower() == FC and connector['wwpns']): - for wwn in connector['wwpns']: - foundinitiatornames.append(wwn) - name = 'world wide port names' + elif self.protocol.lower() == FC: + if ('wwpns' in connector and connector['wwpns']): + for wwn in connector['wwpns']: + foundinitiatornames.append(wwn) + name = 'world wide port names' + else: + msg = (_("FC is the protocol but wwpns are " + "not supplied by Openstack.")) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) if (foundinitiatornames is None or len(foundinitiatornames) == 0): - msg = (_('Error finding %s.') % name) + msg = (_("Error finding %(name)s.") + % {'name': name}) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - LOG.debug("Found %(name)s: %(initiator)s." - % {'name': name, - 'initiator': foundinitiatornames}) + LOG.debug("Found %(name)s: %(initiator)s.", + {'name': name, + 'initiator': foundinitiatornames}) return foundinitiatornames @@ -737,7 +824,7 @@ class EMCVMAXMasking(object): NOTE: An initiator/wwn can only belong to one initiatorGroup. If we were to attempt to create one with an initiator/wwn that is - already belong to another initiatorGroup, it would fail + already belong to another initiatorGroup, it would fail. :param conn: the connection to the ecom server :param controllerConfigService: the controller configuration service @@ -758,7 +845,7 @@ class EMCVMAXMasking(object): conn, initiatorMaskingGroupInstanceName) if instance is None: # MaskingGroup doesn't exist any more. - continue + break storageHardwareIdInstances = ( conn.Associators(initiatorMaskingGroupInstanceName, @@ -804,7 +891,7 @@ class EMCVMAXMasking(object): storageId = hardwareIdInstance['StorageID'] for initiatorName in initiatorNames: if storageId.lower() == initiatorName.lower(): - # Check that the found hardwareId has been deleted. + # Check that the found hardwareId has been delete. # If it has, we don't want to add it to the list. instance = self.utils.get_existing_instance( conn, hardwareIdInstance.path) @@ -817,8 +904,8 @@ class EMCVMAXMasking(object): break LOG.debug( - "The found hardware IDs are : %(foundHardwardIDsInstanceNames)s " - % {'foundHardwardIDsInstanceNames': foundHardwardIDsInstanceNames}) + "The found hardware IDs are : %(foundHardwardIDsInstanceNames)s.", + {'foundHardwardIDsInstanceNames': foundHardwardIDsInstanceNames}) return foundHardwardIDsInstanceNames @@ -868,7 +955,7 @@ class EMCVMAXMasking(object): if rc != 0L: exceptionMessage = (_( "Error Create Masking View: %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': maskingViewName, 'rc': rc, 'error': errordesc}) @@ -876,8 +963,8 @@ class EMCVMAXMasking(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) - LOG.info(_LI("Created new masking view : %(maskingViewName)s ") - % {'maskingViewName': maskingViewName}) + LOG.info(_LI("Created new masking view : %(maskingViewName)s."), + {'maskingViewName': maskingViewName}) return rc, job def find_new_masking_view(self, conn, jobDict): @@ -918,28 +1005,46 @@ class EMCVMAXMasking(object): self._get_storage_group_from_masking_view_instance( conn, foundView)) - LOG.debug("Masking view: %(view)s DeviceMaskingGroup: " - "%(masking)s.", - {'view': maskingViewName, - 'masking': foundStorageGroupInstanceName}) + LOG.debug( + "Masking view: %(view)s DeviceMaskingGroup: %(masking)s.", + {'view': maskingViewName, + 'masking': foundStorageGroupInstanceName}) else: LOG.warn(_LW("Unable to find Masking view: %(view)s."), {'view': maskingViewName}) return foundStorageGroupInstanceName + def _get_storage_group_from_masking_view_instance( + self, conn, maskingViewInstance): + """Gets the Device Masking Group from masking view instance. + + :param conn: the connection to the ecom server + :param maskingViewInstance + :returns: instance name foundStorageGroupInstanceName + """ + foundStorageGroupInstanceName = None + groups = conn.AssociatorNames( + maskingViewInstance, + ResultClass='CIM_DeviceMaskingGroup') + if groups[0] > 0: + foundStorageGroupInstanceName = groups[0] + + return foundStorageGroupInstanceName + def _get_storage_group_instance_name( self, conn, maskingViewDict, defaultStorageGroupInstanceName): """Gets the storage group instance name. - If fastPolicy name is None then NON FAST is assumed. If it is a - valid fastPolicy name then associate the new storage group with - the fast policy. If we are using an existing storage group then - we must check that it is associated with the correct fast policy. + If fastPolicy name is None + then NON FAST is assumed. If it is a valid fastPolicy name + then associate the new storage group with the fast policy. + If we are using an existing storage group then we must check that + it is associated with the correct fast policy. :param conn: the connection to the ecom server - :param maskingViewDict: the masking view dictionary + :param maskingViewDict - the masking view dictionary :param defaultStorageGroupInstanceName: default storage group instance name (can be None for Non FAST) :returns: instance name storageGroupInstanceName @@ -955,7 +1060,7 @@ class EMCVMAXMasking(object): if storageGroupInstanceName is None: errorMessage = (_( "Cannot create or find an storage group with name " - "%(sgGroupName)s") + "%(sgGroupName)s.") % {'sgGroupName': maskingViewDict['sgGroupName']}) LOG.error(errorMessage) raise exception.VolumeBackendAPIException(data=errorMessage) @@ -967,7 +1072,7 @@ class EMCVMAXMasking(object): """Gets the port group instance name. The portGroup name has been defined in the EMC Config file if it - does not exist the operation should fail + does not exist the operation should fail. :param conn: the connection to the ecom server :param controllerConfigService: the controller configuration server @@ -977,16 +1082,15 @@ class EMCVMAXMasking(object): foundPortGroupInstanceName = self._find_port_group( conn, controllerConfigService, pgGroupName) if foundPortGroupInstanceName is None: - errorMessage = (_( + LOG.error(_LE( "Cannot find a portGroup with name %(pgGroupName)s. " - "The port group for a masking view must be pre-defined") - % {'pgGroupName': pgGroupName}) - LOG.error(errorMessage) + "The port group for a masking view must be pre-defined."), + {'pgGroupName': pgGroupName}) return foundPortGroupInstanceName LOG.info(_LI( - "Port group instance name is %(foundPortGroupInstanceName)s") - % {'foundPortGroupInstanceName': foundPortGroupInstanceName}) + "Port group instance name is %(foundPortGroupInstanceName)s."), + {'foundPortGroupInstanceName': foundPortGroupInstanceName}) return foundPortGroupInstanceName @@ -1006,12 +1110,10 @@ class EMCVMAXMasking(object): conn, controllerConfigService, igGroupName, connector, storageSystemName)) if foundInitiatorGroupInstanceName is None: - errorMessage = (_( + LOG.error(_LE( "Cannot create or find an initiator group with " - "name %(igGroupName)s") - % {'igGroupName': igGroupName}) - LOG.error(errorMessage) - + "name %(igGroupName)s."), + {'igGroupName': igGroupName}) return foundInitiatorGroupInstanceName def _get_masking_view_instance_name( @@ -1028,21 +1130,21 @@ class EMCVMAXMasking(object): :param initiatorGroupInstanceName: the initiator group instance name :returns: instance name foundMaskingViewInstanceName """ - rc, job = self._create_masking_view( - conn, controllerConfigService, maskingViewName, - storageGroupInstanceName, portGroupInstanceName, - initiatorGroupInstanceName) + _, job = ( + self._create_masking_view( + conn, controllerConfigService, maskingViewName, + storageGroupInstanceName, portGroupInstanceName, + initiatorGroupInstanceName)) foundMaskingViewInstanceName = self.find_new_masking_view(conn, job) if foundMaskingViewInstanceName is None: - errorMessage = (_( + LOG.error(_LE( "Cannot find the new masking view just created with name " - "%(maskingViewName)s") - % {'maskingViewName': maskingViewName}) - LOG.error(errorMessage) + "%(maskingViewName)s."), + {'maskingViewName': maskingViewName}) return foundMaskingViewInstanceName - def check_if_rollback_action_for_masking_required( + def _check_if_rollback_action_for_masking_required( self, conn, rollbackDict): """This is a rollback action for FAST. @@ -1052,45 +1154,58 @@ class EMCVMAXMasking(object): the exception occurred. :param conn: the connection to the ecom server - :param rollbackDict: information for rolling back + :param controllerConfigService: the controller config service + :param volumeInstanceName: the volume instance name + :param volumeName: the volume name (String) + :param fastPolicyName: the fast policy name (String) + :param defaultStorageGroupInstanceName: the default storage group + instance name """ try: - foundStorageGroupInstanceName = ( - self.utils.get_storage_group_from_volume( - conn, rollbackDict['volumeInstance'].path)) - # Volume is not associated with any storage group so add it back - # to the default. - if len(foundStorageGroupInstanceName) == 0: - LOG.warning(_LW( - "No storage group found. " - "Performing rollback on Volume: %(volumeName)s " - "To return it to the default storage group for FAST policy" - " %(fastPolicyName)s."), - {'volumeName': rollbackDict['volumeName'], - 'fastPolicyName': rollbackDict['fastPolicyName']}) - - assocDefaultStorageGroupName = ( - self.fast - .add_volume_to_default_storage_group_for_fast_policy( - conn, - rollbackDict['controllerConfigService'], - rollbackDict['volumeInstance'], - rollbackDict['volumeName'], - rollbackDict['fastPolicyName'])) - if assocDefaultStorageGroupName is None: - LOG.error(_LE( - "Failed to Roll back to re-add volume %(volumeName)s " - "to default storage group for fast policy " - "%(fastPolicyName)s: Please contact your sys admin to " - "get the volume re-added manually."), + if rollbackDict['isV3']: + errorMessage = self._check_adding_volume_to_storage_group( + conn, rollbackDict, + rollbackDict['defaultStorageGroupInstanceName']) + if errorMessage: + LOG.error(errorMessage) + + else: + foundStorageGroupInstanceName = ( + self.utils.get_storage_group_from_volume( + conn, rollbackDict['volumeInstance'].path)) + # Volume is not associated with any storage group so add + # it back to the default. + if len(foundStorageGroupInstanceName) == 0: + LOG.warn(_LW( + "No storage group found. " + "Performing rollback on Volume: %(volumeName)s " + "To return it to the default storage group for FAST " + "policy %(fastPolicyName)s."), {'volumeName': rollbackDict['volumeName'], 'fastPolicyName': rollbackDict['fastPolicyName']}) - if len(foundStorageGroupInstanceName) > 0: - LOG.info(_LI( - "The storage group found is " - "%(foundStorageGroupInstanceName)s: "), - {'foundStorageGroupInstanceName': - foundStorageGroupInstanceName}) + assocDefaultStorageGroupName = ( + self.fast + .add_volume_to_default_storage_group_for_fast_policy( + conn, + rollbackDict['controllerConfigService'], + rollbackDict['volumeInstance'], + rollbackDict['volumeName'], + rollbackDict['fastPolicyName'])) + if assocDefaultStorageGroupName is None: + LOG.error(_LE( + "Failed to Roll back to re-add volume " + "%(volumeName)s " + "to default storage group for fast policy " + "%(fastPolicyName)s: Please contact your sys " + "admin to get the volume re-added manually."), + {'volumeName': rollbackDict['volumeName'], + 'fastPolicyName': rollbackDict['fastPolicyName']}) + if len(foundStorageGroupInstanceName) > 0: + LOG.info(_LI( + "The storage group found is " + "%(foundStorageGroupInstanceName)s."), + {'foundStorageGroupInstanceName': + foundStorageGroupInstanceName}) # Check the name, see is it the default storage group # or another. @@ -1103,14 +1218,14 @@ class EMCVMAXMasking(object): rollbackDict['controllerConfigService'], rollbackDict['volumeInstance'], rollbackDict['fastPolicyName'], - rollbackDict['volumeName']) + rollbackDict['volumeName'], False) except Exception as e: - LOG.error(_LE("Exception: %s") % six.text_type(e)) + LOG.error(_LE("Exception: %s."), e) errorMessage = (_( "Rollback for Volume: %(volumeName)s has failed. " "Please contact your system administrator to manually return " "your volume to the default storage group for fast policy " - "%(fastPolicyName)s failed ") + "%(fastPolicyName)s failed.") % {'volumeName': rollbackDict['volumeName'], 'fastPolicyName': rollbackDict['fastPolicyName']}) LOG.error(errorMessage) @@ -1143,7 +1258,7 @@ class EMCVMAXMasking(object): foundInitiatorMaskingGroupInstanceName = None foundView = self._find_masking_view( conn, maskingViewName, storageSystemName) - if foundView: + if foundView is not None: groups = conn.AssociatorNames( foundView, ResultClass='CIM_InitiatorMaskingGroup') @@ -1151,12 +1266,12 @@ class EMCVMAXMasking(object): foundInitiatorMaskingGroupInstanceName = groups[0] LOG.debug( - "Masking view: %(view)s InitiatorMaskingGroup: %(masking)s." - % {'view': maskingViewName, - 'masking': foundInitiatorMaskingGroupInstanceName}) + "Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.", + {'view': maskingViewName, + 'masking': foundInitiatorMaskingGroupInstanceName}) else: - LOG.warn(_LW("Unable to find Masking view: %(view)s.") - % {'view': maskingViewName}) + LOG.warn(_LW("Unable to find Masking view: %(view)s."), + {'view': maskingViewName}) return foundInitiatorMaskingGroupInstanceName @@ -1189,7 +1304,7 @@ class EMCVMAXMasking(object): if (foundInitiatorGroupFromConnector != foundInitiatorGroupFromMaskingView): - if foundInitiatorGroupFromMaskingView: + if foundInitiatorGroupFromMaskingView is not None: maskingViewInstanceName = self._find_masking_view( conn, maskingViewName, storageSystemName) if foundInitiatorGroupFromConnector is None: @@ -1199,9 +1314,9 @@ class EMCVMAXMasking(object): if not storageHardwareIDInstanceNames: LOG.error(_LE( "Initiator Name(s) %(initiatorNames)s are not on " - "array %(storageSystemName)s ") - % {'initiatorNames': initiatorNames, - 'storageSystemName': storageSystemName}) + "array %(storageSystemName)s."), + {'initiatorNames': initiatorNames, + 'storageSystemName': storageSystemName}) return False foundInitiatorGroupFromConnector = ( @@ -1213,9 +1328,9 @@ class EMCVMAXMasking(object): conn, maskingViewName, storageSystemName)) portGroupInstanceName = self._get_port_group_from_masking_view( conn, maskingViewName, storageSystemName) - if (foundInitiatorGroupFromConnector and - storageGroupInstanceName and - portGroupInstanceName): + if (foundInitiatorGroupFromConnector is not None and + storageGroupInstanceName is not None and + portGroupInstanceName is not None): self._delete_masking_view( conn, controllerConfigService, maskingViewName, maskingViewInstanceName) @@ -1224,18 +1339,18 @@ class EMCVMAXMasking(object): conn, controllerConfigService, maskingViewName, storageGroupInstanceName, portGroupInstanceName, foundInitiatorGroupFromConnector)) - if newMaskingViewInstanceName: + if newMaskingViewInstanceName is not None: LOG.debug( "The old masking view has been replaced: " - "%(maskingViewName)s. " - % {'maskingViewName': maskingViewName}) + "%(maskingViewName)s.", + {'maskingViewName': maskingViewName}) else: LOG.error(_LE( "One of the components of the original masking view " "%(maskingViewName)s cannot be retrieved so " "please contact your system administrator to check " - "that the correct initiator(s) are part of masking ") - % {'maskingViewName': maskingViewName}) + "that the correct initiator(s) are part of masking."), + {'maskingViewName': maskingViewName}) return False return True @@ -1245,7 +1360,7 @@ class EMCVMAXMasking(object): """Create a new initiator group. Given a list of hardwareId Instance name create a new - initiator group + initiator group. :param conn: connection the ecom server :param controllerConfigService: the controller configuration service @@ -1261,8 +1376,8 @@ class EMCVMAXMasking(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Create Group: %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Create Group: %(groupName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': igGroupName, 'rc': rc, 'error': errordesc}) @@ -1285,7 +1400,7 @@ class EMCVMAXMasking(object): if rc != 0L: exceptionMessage = (_( "Error adding initiator to group : %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': igGroupName, 'rc': rc, 'error': errordesc}) @@ -1305,6 +1420,7 @@ class EMCVMAXMasking(object): :param storageSystemName: the storage system name :returns: instance name foundPortMaskingGroupInstanceName """ + foundPortMaskingGroupInstanceName = None foundView = self._find_masking_view( conn, maskingViewName, storageSystemName) @@ -1316,9 +1432,9 @@ class EMCVMAXMasking(object): foundPortMaskingGroupInstanceName = groups[0] LOG.debug( - "Masking view: %(view)s InitiatorMaskingGroup: %(masking)s." - % {'view': maskingViewName, - 'masking': foundPortMaskingGroupInstanceName}) + "Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.", + {'view': maskingViewName, + 'masking': foundPortMaskingGroupInstanceName}) return foundPortMaskingGroupInstanceName @@ -1341,7 +1457,7 @@ class EMCVMAXMasking(object): if rc != 0L: exceptionMessage = (_( "Error Modifying masking view : %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': maskingViewName, 'rc': rc, 'error': errordesc}) @@ -1354,7 +1470,7 @@ class EMCVMAXMasking(object): """Get the associated maskingview instance name. Given storage group instance name, get the associated masking - view instance name + view instance name. :param conn: connection the ecom server :param storageGroupInstanceName: the storage group instance name @@ -1371,8 +1487,7 @@ class EMCVMAXMasking(object): def add_volume_to_storage_group( self, conn, controllerConfigService, storageGroupInstanceName, - volumeInstance, volumeName, sgGroupName, fastPolicyName, - storageSystemName=None): + volumeInstance, volumeName, sgGroupName, storageSystemName=None): """Add a volume to an existing storage group. :param conn: connection to ecom server @@ -1381,7 +1496,6 @@ class EMCVMAXMasking(object): :param volumeInstance: the volume instance :param volumeName: the name of the volume (String) :param sgGroupName: the name of the storage group (String) - :param fastPolicyName: the fast policy name (String) can be None :param storageSystemName: the storage system name (Optional Parameter), if None plain operation assumed :returns: int rc the return code of the job @@ -1391,12 +1505,11 @@ class EMCVMAXMasking(object): conn, controllerConfigService, storageGroupInstanceName, volumeInstance.path, volumeName) - infoMessage = (_( + LOG.info(_LI( "Added volume: %(volumeName)s to existing storage group " - "%(sgGroupName)s. ") - % {'volumeName': volumeName, - 'sgGroupName': sgGroupName}) - LOG.info(infoMessage) + "%(sgGroupName)s."), + {'volumeName': volumeName, + 'sgGroupName': sgGroupName}) def remove_device_from_default_storage_group( self, conn, controllerConfigService, volumeInstanceName, @@ -1404,7 +1517,7 @@ class EMCVMAXMasking(object): """Remove the volume from the default storage group. Remove the volume from the default storage group for the FAST - policy and return the default storage group instance name + policy and return the default storage group instance name. :param conn: the connection to the ecom server :param controllerConfigService: the controller config service @@ -1420,11 +1533,10 @@ class EMCVMAXMasking(object): volumeName, fastPolicyName)) if defaultStorageGroupInstanceName is None: - errorMessage = (_( + LOG.warn(_LW( "Volume %(volumeName)s was not first part of the default " - "storage group for the FAST Policy") - % {'volumeName': volumeName}) - LOG.warn(errorMessage) + "storage group for the FAST Policy."), + {'volumeName': volumeName}) return failedRet assocVolumeInstanceNames = self.get_devices_from_storage_group( @@ -1432,9 +1544,9 @@ class EMCVMAXMasking(object): LOG.debug( "There are %(length)lu associated with the default storage group " - "for fast before removing volume %(volumeName)s" - % {'length': len(assocVolumeInstanceNames), - 'volumeName': volumeName}) + "for fast before removing volume %(volumeName)s.", + {'length': len(assocVolumeInstanceNames), + 'volumeName': volumeName}) self.provision.remove_device_from_storage_group( conn, controllerConfigService, defaultStorageGroupInstanceName, @@ -1444,20 +1556,19 @@ class EMCVMAXMasking(object): conn, defaultStorageGroupInstanceName) LOG.debug( "There are %(length)lu associated with the default storage group " - "for fast after removing volume %(volumeName)s" - % {'length': len(assocVolumeInstanceNames), - 'volumeName': volumeName}) + "for fast after removing volume %(volumeName)s.", + {'length': len(assocVolumeInstanceNames), + 'volumeName': volumeName}) # Required for unit tests. emptyStorageGroupInstanceName = ( self._wrap_get_storage_group_from_volume(conn, volumeInstanceName)) if emptyStorageGroupInstanceName is not None: - errorMessage = (_( + LOG.error(_LE( "Failed to remove %(volumeName)s from the default storage " - "group for the FAST Policy") - % {'volumeName': volumeName}) - LOG.error(errorMessage) + "group for the FAST Policy."), + {'volumeName': volumeName}) return failedRet return defaultStorageGroupInstanceName @@ -1466,7 +1577,7 @@ class EMCVMAXMasking(object): """Wrapper for get_storage_group_from_volume. - Needed for override in tests + Needed for override in tests. :param conn: the connection to the ecom server :param volumeInstanceName: the volume instance name @@ -1480,7 +1591,7 @@ class EMCVMAXMasking(object): """Get the associated volume Instance names. Given the storage group instance name get the associated volume - Instance names + Instance names. :param conn: connection the the ecom server :param storageGroupInstanceName: the storage group instance name @@ -1516,22 +1627,24 @@ class EMCVMAXMasking(object): def remove_and_reset_members( self, conn, controllerConfigService, volumeInstance, - fastPolicyName, volumeName, connector=None): + fastPolicyName, volumeName, isV3, connector=None, noReset=None): """Part of unmap device or rollback. Removes volume from the Device Masking Group that belongs to a Masking View. Check if fast policy is in the extra specs, if it isn't we do not need to do any thing for FAST. Assume that isTieringPolicySupported is False unless the FAST policy is in - the extra specs and tiering is enabled on the array + the extra specs and tiering is enabled on the array. :param conn: connection the the ecom server :param controllerConfigService: the controller configuration service :param volumeInstance: the volume Instance :param fastPolicyName: the fast policy name (if it exists) :param volumeName: the volume name - :param connector: the connector object - :returns: storage group instance name + :param isV3: is array v2 or v3 + :param connector: optional + :param noReset: optional, if none, then reset + :returns: maskingGroupInstanceName """ storageGroupInstanceName = None if connector is not None: @@ -1540,7 +1653,7 @@ class EMCVMAXMasking(object): volumeName, connector) if storageGroupInstanceName is None: return None - else: + else: # Connector is None in V3 volume deletion case. storageGroupInstanceNames = ( self.get_associated_masking_groups_from_device( conn, volumeInstance.path)) @@ -1548,6 +1661,8 @@ class EMCVMAXMasking(object): storageGroupInstanceName = storageGroupInstanceNames[0] else: return None + instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False) + storageGroupName = instance['ElementName'] volumeInstanceNames = self.get_devices_from_storage_group( conn, storageGroupInstanceName) @@ -1557,22 +1672,26 @@ class EMCVMAXMasking(object): numVolInMaskingView = len(volumeInstanceNames) LOG.debug( "There are %(numVol)d volumes in the storage group " - "%(maskingGroup)s", + "%(maskingGroup)s.", {'numVol': numVolInMaskingView, 'maskingGroup': storageGroupInstanceName}) - isTieringPolicySupported, tierPolicyServiceInstanceName = ( - self._get_tiering_info(conn, storageSystemInstanceName, - fastPolicyName)) + + if not isV3: + isTieringPolicySupported, tierPolicyServiceInstanceName = ( + self._get_tiering_info(conn, storageSystemInstanceName, + fastPolicyName)) + if numVolInMaskingView == 1: # Last volume in the storage group. self._last_volume_delete_masking_view( conn, storageGroupInstanceName) - self._get_and_remove_rule_association( - conn, fastPolicyName, - isTieringPolicySupported, - tierPolicyServiceInstanceName, - storageSystemInstanceName['name'], - storageGroupInstanceName) + if not isV3: + self._get_and_remove_rule_association( + conn, fastPolicyName, + isTieringPolicySupported, + tierPolicyServiceInstanceName, + storageSystemInstanceName['Name'], + storageGroupInstanceName) self.provision.remove_device_from_storage_group( conn, controllerConfigService, storageGroupInstanceName, @@ -1585,16 +1704,21 @@ class EMCVMAXMasking(object): # Delete storage group. conn.DeleteInstance(storageGroupInstanceName) - - if isTieringPolicySupported: - self._cleanup_tiering( - conn, controllerConfigService, fastPolicyName, - volumeInstance, volumeName) + if isV3: + if noReset is None: + self._return_volume_to_default_storage_group_v3( + conn, controllerConfigService, storageGroupName, + volumeInstance, volumeName, storageSystemInstanceName) + else: + if isTieringPolicySupported: + self._cleanup_tiering( + conn, controllerConfigService, fastPolicyName, + volumeInstance, volumeName) else: # Not the last volume so remove it from storage group in # the masking view. - LOG.debug("start: number of volumes in masking storage group: " - "%(numVol)d", {'numVol': len(volumeInstanceNames)}) + LOG.debug("Start: number of volumes in masking storage group: " + "%(numVol)d.", {'numVol': len(volumeInstanceNames)}) self.provision.remove_device_from_storage_group( conn, controllerConfigService, storageGroupInstanceName, volumeInstance.path, volumeName) @@ -1602,117 +1726,28 @@ class EMCVMAXMasking(object): LOG.debug( "RemoveMembers for volume %(volumeName)s completed " "successfully.", {'volumeName': volumeName}) - # If FAST POLICY enabled, move the volume to the default SG. - if fastPolicyName is not None and isTieringPolicySupported: - self._cleanup_tiering( - conn, controllerConfigService, fastPolicyName, - volumeInstance, volumeName) + + # Add it back to the default storage group. + if isV3: + self._return_volume_to_default_storage_group_v3( + conn, controllerConfigService, storageGroupName, + volumeInstance, volumeName, storageSystemInstanceName) + else: + # V2 if FAST POLICY enabled, move the volume to the default + # SG. + if fastPolicyName is not None and isTieringPolicySupported: + self._cleanup_tiering( + conn, controllerConfigService, fastPolicyName, + volumeInstance, volumeName) volumeInstanceNames = self.get_devices_from_storage_group( conn, storageGroupInstanceName) LOG.debug( - "end: number of volumes in masking storage group: %(numVol)d", + "End: number of volumes in masking storage group: %(numVol)d.", {'numVol': len(volumeInstanceNames)}) return storageGroupInstanceName - def _cleanup_tiering( - self, conn, controllerConfigService, fastPolicyName, - volumeInstance, volumeName): - """Clean up tiering. - - :param conn: the ecom connection - :param controllerConfigService: the controller configuration service - :param fastPolicyName: the fast policy name - :param volumeInstance: volume instance - :param volumeName: the volume name - """ - defaultStorageGroupInstanceName = ( - self.fast.get_policy_default_storage_group( - conn, controllerConfigService, fastPolicyName)) - volumeInstanceNames = self.get_devices_from_storage_group( - conn, defaultStorageGroupInstanceName) - LOG.debug( - "start: number of volumes in default storage group: %(numVol)d" - % {'numVol': len(volumeInstanceNames)}) - defaultStorageGroupInstanceName = ( - self.fast.add_volume_to_default_storage_group_for_fast_policy( - conn, controllerConfigService, volumeInstance, volumeName, - fastPolicyName)) - # Check default storage group number of volumes. - volumeInstanceNames = self.get_devices_from_storage_group( - conn, defaultStorageGroupInstanceName) - LOG.debug( - "end: number of volumes in default storage group: %(numVol)d" - % {'numVol': len(volumeInstanceNames)}) - - def get_target_wwns(self, conn, mvInstanceName): - """Get the DA ports' wwns. - - :param conn: the ecom connection - :param mvInstanceName: masking view instance name - """ - targetWwns = [] - targetPortInstanceNames = conn.AssociatorNames( - mvInstanceName, - ResultClass='Symm_FCSCSIProtocolEndpoint') - numberOfPorts = len(targetPortInstanceNames) - if numberOfPorts <= 0: - LOG.warning(_LW("No target ports found in " - "masking view %(maskingView)s"), - {'numPorts': len(targetPortInstanceNames), - 'maskingView': mvInstanceName}) - for targetPortInstanceName in targetPortInstanceNames: - targetWwns.append(targetPortInstanceName['Name']) - return targetWwns - - def get_masking_view_by_volume(self, conn, volumeInstance, connector): - """Given volume, retrieve the masking view instance name. - - :param volumeInstance: the volume instance - :param connector: the connector object - :returns mvInstanceName: masking view instance name - """ - - storageSystemName = volumeInstance['SystemName'] - controllerConfigService = ( - self.utils.find_controller_configuration_service( - conn, storageSystemName)) - volumeName = volumeInstance['ElementName'] - mvInstanceName = ( - self._get_sg_or_mv_associated_with_initiator( - conn, controllerConfigService, volumeInstance.path, - volumeName, connector, False)) - return mvInstanceName - - def get_masking_views_by_port_group(self, conn, portGroupInstanceName): - """Given port group, retrieve the masking view instance name. - - :param : the volume - :param mvInstanceName: masking view instance name - :returns: maksingViewInstanceNames - """ - mvInstanceNames = conn.AssociatorNames( - portGroupInstanceName, ResultClass='Symm_LunMaskingView') - return mvInstanceNames - - def get_port_group_from_masking_view(self, conn, maskingViewInstanceName): - """Get the port group in a masking view. - - :param maskingViewInstanceName: masking view instance name - :returns: portGroupInstanceName - """ - portGroupInstanceNames = conn.AssociatorNames( - maskingViewInstanceName, ResultClass='SE_TargetMaskingGroup') - if len(portGroupInstanceNames) > 0: - LOG.debug("Found port group %(pg)s in masking view %(mv)s" - % {'pg': portGroupInstanceNames[0], - 'mv': maskingViewInstanceName}) - return portGroupInstanceNames[0] - else: - LOG.warning(_LW("No port group found in masking view %(mv)s"), - {'mv': maskingViewInstanceName}) - def _get_sg_associated_with_connector( self, conn, controllerConfigService, volumeInstanceName, volumeName, connector): @@ -1762,8 +1797,8 @@ class EMCVMAXMasking(object): self, conn, storageGroupInstanceName): """Delete the masking view. - delete the masking view if the volume is the last one in the - storage group + Delete the masking view if the volume is the last one in the + storage group. :param conn: the ecom connection :param storageGroupInstanceName: storage group instance name @@ -1774,7 +1809,7 @@ class EMCVMAXMasking(object): if mvInstanceName is not None: LOG.debug( "Last volume in the storage group, deleting masking view " - "%(mvInstanceName)s", + "%(mvInstanceName)s.", {'mvInstanceName': mvInstanceName}) conn.DeleteInstance(mvInstanceName) @@ -1796,8 +1831,8 @@ class EMCVMAXMasking(object): conn, storageSystemName, fastPolicyName) LOG.info(_LI( - "policy:%(policy)s, policy service:%(service)s, " - "masking group=%(maskingGroup)s"), + "Policy: %(policy)s, policy service:%(service)s, " + "masking group: %(maskingGroup)s."), {'policy': tierPolicyInstanceName, 'service': tierPolicyServiceInstanceName, 'maskingGroup': storageGroupInstanceName}) @@ -1806,6 +1841,146 @@ class EMCVMAXMasking(object): conn, tierPolicyServiceInstanceName, storageGroupInstanceName, tierPolicyInstanceName) + def _return_volume_to_default_storage_group_v3( + self, conn, controllerConfigService, storageGroupName, + volumeInstance, volumeName, storageSystemInstanceName): + """Return volume to the default storage group in v3. + + :param conn: the ecom connection + :param controllerConfigService: controller config service + :param storageGroupInstanceName: storage group instance name + :param volumeInstance: volumeInstance + :param volumeName: the volume name + :param storageSystemInstanceName: the storage system instance name + """ + # First strip the shortHostname from the storage group name. + defaultStorageGroupName, shorthostName = ( + self.utils.strip_short_host_name(storageGroupName)) + + # Check if host name exists which signifies detach operation. + if shorthostName is not None: + # Populate maskingViewDict and storageGroupInstanceName. + maskingViewDict = {} + maskingViewDict['sgGroupName'] = defaultStorageGroupName + maskingViewDict['volumeInstance'] = volumeInstance + maskingViewDict['volumeName'] = volumeName + maskingViewDict['controllerConfigService'] = \ + controllerConfigService + maskingViewDict['storageSystemName'] = \ + storageSystemInstanceName + sgInstanceName = self.utils.find_storage_masking_group( + conn, controllerConfigService, defaultStorageGroupName) + if sgInstanceName is not None: + errorMessage = ( + self._check_adding_volume_to_storage_group( + conn, maskingViewDict, + sgInstanceName)) + else: + errorMessage = (_( + "Storage group %(sgGroupName) " + "does not exist.") + % {'StorageGroup': defaultStorageGroupName}) + LOG.error(errorMessage) + raise exception.VolumeBackendAPIException( + data=errorMessage) + + def _cleanup_tiering( + self, conn, controllerConfigService, fastPolicyName, + volumeInstance, volumeName): + """Clea nup tiering. + + :param conn: the ecom connection + :param controllerConfigService: the controller configuration service + :param fastPolicyName: the fast policy name + :param volumeInstance: volume instance + :param volumeName: the volume name + """ + defaultStorageGroupInstanceName = ( + self.fast.get_policy_default_storage_group( + conn, controllerConfigService, fastPolicyName)) + volumeInstanceNames = self.get_devices_from_storage_group( + conn, defaultStorageGroupInstanceName) + LOG.debug( + "Start: number of volumes in default storage group: %(numVol)d.", + {'numVol': len(volumeInstanceNames)}) + defaultStorageGroupInstanceName = ( + self.fast.add_volume_to_default_storage_group_for_fast_policy( + conn, controllerConfigService, volumeInstance, volumeName, + fastPolicyName)) + # Check default storage group number of volumes. + volumeInstanceNames = self.get_devices_from_storage_group( + conn, defaultStorageGroupInstanceName) + LOG.debug( + "End: number of volumes in default storage group: %(numVol)d.", + {'numVol': len(volumeInstanceNames)}) + + def get_target_wwns(self, conn, mvInstanceName): + """Get the DA ports wwns. + + :param conn: the ecom connection + :param mvInstanceName: masking view instance name + """ + targetWwns = [] + targetPortInstanceNames = conn.AssociatorNames( + mvInstanceName, + ResultClass='Symm_FCSCSIProtocolEndpoint') + numberOfPorts = len(targetPortInstanceNames) + if numberOfPorts <= 0: + LOG.warn(_LW("No target ports found in " + "masking view %(maskingView)s."), + {'numPorts': len(targetPortInstanceNames), + 'maskingView': mvInstanceName}) + for targetPortInstanceName in targetPortInstanceNames: + targetWwns.append(targetPortInstanceName['Name']) + return targetWwns + + def get_masking_view_by_volume(self, conn, volumeInstance, connector): + """Given volume, retrieve the masking view instance name. + + :param volumeInstance: the volume instance + :param connector: the connector object + :returns mvInstanceName: masking view instance name + """ + + storageSystemName = volumeInstance['SystemName'] + controllerConfigService = ( + self.utils.find_controller_configuration_service( + conn, storageSystemName)) + volumeName = volumeInstance['ElementName'] + mvInstanceName = ( + self._get_sg_or_mv_associated_with_initiator( + conn, controllerConfigService, volumeInstance.path, + volumeName, connector, False)) + return mvInstanceName + + def get_masking_views_by_port_group(self, conn, portGroupInstanceName): + """Given port group, retrieve the masking view instance name. + + :param : the volume + :param mvInstanceName: masking view instance name + :returns: maksingViewInstanceNames + """ + mvInstanceNames = conn.AssociatorNames( + portGroupInstanceName, ResultClass='Symm_LunMaskingView') + return mvInstanceNames + + def get_port_group_from_masking_view(self, conn, maskingViewInstanceName): + """Get the port group in a masking view. + + :param maskingViewInstanceName: masking view instance name + :returns: portGroupInstanceName + """ + portGroupInstanceNames = conn.AssociatorNames( + maskingViewInstanceName, ResultClass='SE_TargetMaskingGroup') + if len(portGroupInstanceNames) > 0: + LOG.debug("Found port group %(pg)s in masking view %(mv)s.", + {'pg': portGroupInstanceNames[0], + 'mv': maskingViewInstanceName}) + return portGroupInstanceNames[0] + else: + LOG.warn(_LW("No port group found in masking view %(mv)s."), + {'mv': maskingViewInstanceName}) + def get_initiator_group_from_masking_view( self, conn, maskingViewInstanceName): """Get initiator group in a masking view. diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 5261e2969..9b2bfb9d2 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,6 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import time import six @@ -20,6 +21,7 @@ from cinder.i18n import _, _LE from cinder.openstack.common import log as logging from cinder.volume.drivers.emc import emc_vmax_utils + LOG = logging.getLogger(__name__) STORAGEGROUPTYPE = 4 @@ -48,18 +50,27 @@ class EMCVMAXProvision(object): :param storageConfigservice: volume created from job :param volumeInstanceName: the volume instance name :param volumeName: the volume name (String) + :param :param rc: return code """ + startTime = time.time() + + if isinstance(volumeInstanceName, list): + theElements = volumeInstanceName + volumeName = 'Bulk Delete' + else: + theElements = [volumeInstanceName] + rc, job = conn.InvokeMethod( 'EMCReturnToStoragePool', storageConfigservice, - TheElements=[volumeInstanceName]) + TheElements=theElements) if rc != 0L: rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Delete Volume: %(volumeName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Delete Volume: %(volumeName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'volumeName': volumeName, 'rc': rc, 'error': errordesc}) @@ -67,6 +78,11 @@ class EMCVMAXProvision(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod EMCReturnToStoragePool took: " + "%(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc def create_volume_from_pool( @@ -82,6 +98,8 @@ class EMCVMAXProvision(object): :param volumeSize: volume size (String) :returns: volumeDict - the volume dict """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'CreateOrModifyElementFromStoragePool', storageConfigService, ElementName=volumeName, @@ -90,16 +108,16 @@ class EMCVMAXProvision(object): Size=self.utils.get_num(volumeSize, '64'), EMCBindElements=False) - LOG.debug("Create Volume: %(volumename)s Return code: %(rc)lu" - % {'volumename': volumeName, - 'rc': rc}) + LOG.debug("Create Volume: %(volumename)s Return code: %(rc)lu.", + {'volumename': volumeName, + 'rc': rc}) if rc != 0L: rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Create Volume: %(volumeName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Create Volume: %(volumeName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'volumeName': volumeName, 'rc': rc, 'error': errordesc}) @@ -107,9 +125,13 @@ class EMCVMAXProvision(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) - # Find the newly created volume - volumeDict = self.get_volume_dict_from_job(conn, job['Job']) + LOG.debug("InvokeMethod CreateOrModifyElementFromStoragePool " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + # Find the newly created volume. + volumeDict = self.get_volume_dict_from_job(conn, job['Job']) return volumeDict, rc def create_and_get_storage_group(self, conn, controllerConfigService, @@ -123,6 +145,8 @@ class EMCVMAXProvision(object): :returns: foundStorageGroupInstanceName - instance name of the default storage group """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'CreateGroup', controllerConfigService, GroupName=storageGroupName, Type=self.utils.get_num(STORAGEGROUPTYPE, '16'), @@ -132,14 +156,19 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Create Group: %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Create Group: %(groupName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': storageGroupName, 'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod CreateGroup " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) foundStorageGroupInstanceName = self._find_new_storage_group( conn, job, storageGroupName) @@ -155,6 +184,8 @@ class EMCVMAXProvision(object): :returns: foundStorageGroupInstanceName - the instance Name of the storage group """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'CreateGroup', controllerConfigService, GroupName=groupName, Type=self.utils.get_num(STORAGEGROUPTYPE, '16'), @@ -164,14 +195,20 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Create Group: %(groupName)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Create Group: %(groupName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'groupName': groupName, 'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod CreateGroup " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + foundStorageGroupInstanceName = self._find_new_storage_group( conn, job, groupName) @@ -226,6 +263,8 @@ class EMCVMAXProvision(object): :param volumeName: the volume name (String) :returns: rc - the return code of the job """ + startTime = time.time() + rc, jobDict = conn.InvokeMethod('RemoveMembers', controllerConfigService, MaskingGroup=storageGroupInstanceName, @@ -234,24 +273,32 @@ class EMCVMAXProvision(object): rc, errorDesc = self.utils.wait_for_job_complete(conn, jobDict) if rc != 0L: exceptionMessage = (_( - "Error removing volume %(vol)s. %(error)s") + "Error removing volume %(vol)s. %(error)s.") % {'vol': volumeName, 'error': errorDesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod RemoveMembers " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc def add_members_to_masking_group( self, conn, controllerConfigService, storageGroupInstanceName, volumeInstanceName, volumeName): """Add a member to a masking group group. + :param conn: the connection to the ecom server :param controllerConfigService: the controller configuration service :param storageGroupInstanceName: the instance name of the storage group :param volumeInstanceName: the instance name of the volume :param volumeName: the volume name (String) """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'AddMembers', controllerConfigService, MaskingGroup=storageGroupInstanceName, @@ -261,12 +308,17 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error mapping volume %(vol)s. %(error)s") + "Error mapping volume %(vol)s. %(error)s.") % {'vol': volumeName, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod AddMembers " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + def unbind_volume_from_storage_pool( self, conn, storageConfigService, poolInstanceName, volumeInstanceName, volumeName): @@ -280,6 +332,8 @@ class EMCVMAXProvision(object): :param volumeName: the volume name :returns: unboundVolumeInstance - the unbound volume instance """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'EMCUnBindElement', storageConfigService, @@ -290,12 +344,17 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error unbinding volume %(vol)s from pool. %(error)s") + "Error unbinding volume %(vol)s from pool. %(error)s.") % {'vol': volumeName, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod EMCUnBindElement " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job def modify_composite_volume( @@ -312,6 +371,8 @@ class EMCVMAXProvision(object): :returns: rc - return code :returns: job - job """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'CreateOrModifyCompositeElement', elementCompositionService, @@ -323,11 +384,16 @@ class EMCVMAXProvision(object): if rc != 0L: exceptionMessage = (_( "Error adding volume to composite volume. " - "Error is: %(error)s") + "Error is: %(error)s.") % {'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod CreateOrModifyCompositeElement " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) return rc, job def create_composite_volume( @@ -347,8 +413,9 @@ class EMCVMAXProvision(object): :returns: rc :returns: errordesc """ - newMembers = 2 + startTime = time.time() + newMembers = 2 LOG.debug( "Parameters for CreateOrModifyCompositeElement: " "elementCompositionService: %(elementCompositionService)s " @@ -357,14 +424,14 @@ class EMCVMAXProvision(object): "newMembers: %(newMembers)lu " "poolInstanceName: %(poolInstanceName)s " "compositeType: %(compositeType)lu " - "numMembers: %(numMembers)s " - % {'elementCompositionService': elementCompositionService, - 'provisioning': THINPROVISIONINGCOMPOSITE, - 'volumeSize': volumeSize, - 'newMembers': newMembers, - 'poolInstanceName': poolInstanceName, - 'compositeType': compositeType, - 'numMembers': numMembers}) + "numMembers: %(numMembers)s.", + {'elementCompositionService': elementCompositionService, + 'provisioning': THINPROVISIONINGCOMPOSITE, + 'volumeSize': volumeSize, + 'newMembers': newMembers, + 'poolInstanceName': poolInstanceName, + 'compositeType': compositeType, + 'numMembers': numMembers}) rc, job = conn.InvokeMethod( 'CreateOrModifyCompositeElement', elementCompositionService, @@ -380,8 +447,8 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Create Volume: %(volumename)s. " - "Return code: %(rc)lu. Error: %(error)s") + "Error Create Volume: %(volumename)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'volumename': volumeName, 'rc': rc, 'error': errordesc}) @@ -389,6 +456,11 @@ class EMCVMAXProvision(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod CreateOrModifyCompositeElement " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + # Find the newly created volume. volumeDict = self.get_volume_dict_from_job(conn, job['Job']) @@ -411,6 +483,8 @@ class EMCVMAXProvision(object): :returns: rc - return code :returns: errordesc - descriptions of the error """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'CreateOrModifyCompositeElement', elementCompositionService, ElementType=self.utils.get_num('2', '16'), @@ -422,13 +496,19 @@ class EMCVMAXProvision(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( - "Error Creating new composite Volume Return code: %(rc)lu." - "Error: %(error)s") + "Error Creating new composite Volume Return code: " + "%(rc)lu. Error: %(error)s.") % {'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod CreateOrModifyCompositeElement " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job def _migrate_volume( @@ -443,6 +523,8 @@ class EMCVMAXProvision(object): :param targetPoolInstanceName: the target pool to migrate the volume to :returns: rc - return code """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'RelocateStorageVolumesToStoragePool', storageRelocationServiceInstanceName, @@ -454,12 +536,17 @@ class EMCVMAXProvision(object): if rc != 0L: exceptionMessage = (_( "Error Migrating volume from one pool to another. " - "Return code: %(rc)lu. Error: %(error)s") + "Return code: %(rc)lu. Error: %(error)s.") % {'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + LOG.debug("InvokeMethod RelocateStorageVolumesToStoragePool " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc def migrate_volume_to_storage_pool( @@ -477,9 +564,9 @@ class EMCVMAXProvision(object): """ LOG.debug( "Volume instance name is %(volumeInstanceName)s. " - "Pool instance name is : %(targetPoolInstanceName)s. " - % {'volumeInstanceName': volumeInstanceName, - 'targetPoolInstanceName': targetPoolInstanceName}) + "Pool instance name is : %(targetPoolInstanceName)s. ", + {'volumeInstanceName': volumeInstanceName, + 'targetPoolInstanceName': targetPoolInstanceName}) rc = -1 try: rc = self._migrate_volume( @@ -491,9 +578,9 @@ class EMCVMAXProvision(object): rc = self._terminate_migrate_session( conn, volumeInstanceName) except Exception as ex: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) + LOG.error(_LE('Exception: %s.'), ex) exceptionMessage = (_( - "Failed to terminate migrate session")) + "Failed to terminate migrate session.")) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) @@ -502,17 +589,17 @@ class EMCVMAXProvision(object): conn, storageRelocationServiceInstanceName, volumeInstanceName, targetPoolInstanceName) except Exception as ex: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) + LOG.error(_LE('Exception: %s'), ex) exceptionMessage = (_( - "Failed to migrate volume for the second time")) + "Failed to migrate volume for the second time.")) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) else: - LOG.error(_LE("Exception: %s") % six.text_type(ex)) + LOG.error(_LE('Exception: %s'), ex) exceptionMessage = (_( - "Failed to migrate volume for the first time")) + "Failed to migrate volume for the first time.")) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) @@ -526,6 +613,8 @@ class EMCVMAXProvision(object): :param volumeInstanceName: the volume to be migrated :returns: rc """ + startTime = time.time() + rc, job = conn.InvokeMethod( 'RequestStateChange', volumeInstanceName, RequestedState=self.utils.get_num(32769, '16')) @@ -534,41 +623,98 @@ class EMCVMAXProvision(object): if rc != 0L: exceptionMessage = (_( "Error Terminating migrate session. " - "Return code: %(rc)lu. Error: %(error)s") + "Return code: %(rc)lu. Error: %(error)s.") % {'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod RequestStateChange " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc def create_element_replica( self, conn, repServiceInstanceName, cloneName, - sourceName, sourceInstance, targetInstance): + sourceName, sourceInstance, targetInstance, copyOnWrite=False): """Make SMI-S call to create replica for source element. - :param conn: the connection to the ecom server - :param repServiceInstanceName: instance name of the replication service - :param cloneName: replica name - :param sourceName: source volume name - :param sourceInstance: source volume instance - :param targetInstance: target volume instance + :param conn - the connection to the ecom server + :param repServiceInstanceName - replication service + :param cloneName - replica name + :param sourceName - source volume name + :param sourceInstance - source volume instance + :returns: rc - return code :returns: job - job object of the replica creation operation """ - if targetInstance is None: + if copyOnWrite: + startTime = time.time() + repServiceCapabilityInstanceNames = conn.AssociatorNames( + repServiceInstanceName, + ResultClass='CIM_ReplicationServiceCapabilities', + AssocClass='CIM_ElementCapabilities') + repServiceCapabilityInstanceName = ( + repServiceCapabilityInstanceNames[0]) + + # ReplicationType 10 - Synchronous Clone Local. + rc, rsd = conn.InvokeMethod( + 'GetDefaultReplicationSettingData', + repServiceCapabilityInstanceName, + ReplicationType=self.utils.get_num(10, '16')) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, rsd) + if rc != 0L: + exceptionMessage = (_( + "Error creating cloned volume using " + "Volume: %(cloneName)s, Source Volume: " + "%(sourceName)s. Return code: %(rc)lu. " + "Error: %(error)s.") + % {'cloneName': cloneName, + 'sourceName': sourceName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod GetDefaultReplicationSettingData " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + # Set DesiredCopyMethodology to Copy-On-Write (6). + rsdInstance = rsd['DefaultInstance'] + rsdInstance['DesiredCopyMethodology'] = self.utils.get_num(6, '16') + + startTime = time.time() + + # SyncType 8 - Clone. + # ReplicationSettingData.DesiredCopyMethodology Copy-On-Write (6). rc, job = conn.InvokeMethod( 'CreateElementReplica', repServiceInstanceName, - ElementName=cloneName, - SyncType=self.utils.get_num(8, '16'), + ElementName=cloneName, SyncType=self.utils.get_num(8, '16'), + ReplicationSettingData=rsdInstance, SourceElement=sourceInstance.path) else: - rc, job = conn.InvokeMethod( - 'CreateElementReplica', repServiceInstanceName, - ElementName=cloneName, - SyncType=self.utils.get_num(8, '16'), - SourceElement=sourceInstance.path, - TargetElement=targetInstance.path) + startTime = time.time() + if targetInstance is None: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=self.utils.get_num(8, '16'), + SourceElement=sourceInstance.path) + else: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=self.utils.get_num(8, '16'), + SourceElement=sourceInstance.path, + TargetElement=targetInstance.path) if rc != 0L: rc, errordesc = self.utils.wait_for_job_complete(conn, job) @@ -577,7 +723,7 @@ class EMCVMAXProvision(object): "Error Create Cloned Volume: " "Volume: %(cloneName)s Source Volume:" "%(sourceName)s. Return code: %(rc)lu. " - "Error: %(error)s") + "Error: %(error)s.") % {'cloneName': cloneName, 'sourceName': sourceName, 'rc': rc, @@ -585,61 +731,65 @@ class EMCVMAXProvision(object): LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod CreateElementReplica " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) return rc, job def delete_clone_relationship( - self, conn, repServiceInstanceName, syncInstanceName, - cloneName, sourceName): + self, conn, repServiceInstanceName, syncInstanceName, force=False): """Deletes the relationship between the clone and source volume. Makes an SMI-S call to break clone relationship between the clone - volume and the source + volume and the source. 8/Detach - Delete the synchronization between + two storage objects. Treat the objects as independent after the + synchronization is deleted. :param conn: the connection to the ecom server :param repServiceInstanceName: instance name of the replication service :param syncInstanceName: instance name of the SE_StorageSynchronized_SV_SV object - :param cloneName: replica name - :param sourceName: source volume name - :param sourceInstance: source volume instance :returns: rc - return code :returns: job - job object of the replica creation operation """ + startTime = time.time() - ''' - 8/Detach - Delete the synchronization between two storage objects. - Treat the objects as independent after the synchronization is deleted. - ''' rc, job = conn.InvokeMethod( 'ModifyReplicaSynchronization', repServiceInstanceName, Operation=self.utils.get_num(8, '16'), - Synchronization=syncInstanceName) + Synchronization=syncInstanceName, + Force=force) - LOG.debug("Break clone relationship: Volume: %(cloneName)s " - "Source Volume: %(sourceName)s Return code: %(rc)lu" - % {'cloneName': cloneName, - 'sourceName': sourceName, - 'rc': rc}) + LOG.debug("Delete clone relationship: Sync Name: %(syncName)s " + "Return code: %(rc)lu.", + {'syncName': syncInstanceName, + 'rc': rc}) if rc != 0L: rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: exceptionMessage = (_( "Error break clone relationship: " - "Clone Volume: %(cloneName)s " - "Source Volume: %(sourceName)s. " - "Return code: %(rc)lu. Error: %(error)s") - % {'cloneName': cloneName, - 'sourceName': sourceName, + "Sync Name: %(syncName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'syncName': syncInstanceName, 'rc': rc, 'error': errordesc}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException( data=exceptionMessage) + + LOG.debug("InvokeMethod ModifyReplicaSynchronization " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job def get_target_endpoints(self, conn, storageHardwareService, hardwareId): - """Given the hardwareId, get the target end points. + """Given the hardwareId get the target endpoints. :param conn: the connection to the ecom server :param storageHardwareService: the storage HardwareId Service @@ -647,6 +797,8 @@ class EMCVMAXProvision(object): :returns: rc :returns: targetendpoints """ + startTime = time.time() + rc, targetEndpoints = conn.InvokeMethod( 'EMCGetTargetEndpoints', storageHardwareService, HardwareId=hardwareId) @@ -656,4 +808,223 @@ class EMCVMAXProvision(object): LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) + LOG.debug("InvokeMethod EMCGetTargetEndpoints " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, targetEndpoints + + def create_consistency_group( + self, conn, replicationService, consistencyGroupName): + """Create a new consistency group. + + :param conn: the connection to the ecom server + :param replicationService: the replication Service + :param consistencyGroupName: the CG group name + :returns: rc + :returns: job + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'CreateGroup', + replicationService, + GroupName=consistencyGroupName) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Failed to create consistency group: " + "%(consistencyGroupName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'consistencyGroupName': consistencyGroupName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod CreateGroup " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + return rc, job + + def delete_consistency_group( + self, conn, replicationService, cgInstanceName, + consistencyGroupName): + + """Delete a consistency group. + + :param conn: the connection to the ecom server + :param replicationService: the replication Service + :param cgInstanceName: the CG instance name + :param consistencyGroupName: the CG group name + :returns: rc + :returns: job + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'DeleteGroup', + replicationService, + ReplicationGroup=cgInstanceName, + RemoveElements=True) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Failed to delete consistency group: " + "%(consistencyGroupName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'consistencyGroupName': consistencyGroupName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod DeleteGroup " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + return rc, job + + def add_volume_to_cg( + self, conn, replicationService, cgInstanceName, + volumeInstanceName, cgName, volumeName): + """Add a volume to a consistency group. + + :param conn: the connection to the ecom server + :param replicationService: the replication Service + :param volumeInstanceName: the volume instance name + :param cgInstanceName: the CG instance name + :param cgName: the CG group name + :param volumeName: the volume name + :returns: rc + :returns: job + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'AddMembers', + replicationService, + Members=[volumeInstanceName], + ReplicationGroup=cgInstanceName) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Failed to add volume %(volumeName)s: " + "to consistency group %(cgName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'volumeName': volumeName, + 'cgName': cgName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod AddMembers " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job + + def remove_volume_from_cg( + self, conn, replicationService, cgInstanceName, + volumeInstanceName, cgName, volumeName): + """Remove a volume from a consistency group. + + :param conn: the connection to the ecom server + :param replicationService: the replication Service + :param volumeInstanceName: the volume instance name + :param cgInstanceName: the CG instance name + :param cgName: the CG group name + :param volumeName: the volume name + :returns: rc + :returns: job + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'RemoveMembers', + replicationService, + Members=[volumeInstanceName], + ReplicationGroup=cgInstanceName, + RemoveElements=True) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Failed to remove volume %(volumeName)s: " + "to consistency group %(cgName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'volumeName': volumeName, + 'cgName': cgName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod RemoveMembers " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job + + def create_group_replica( + self, conn, replicationService, + srcGroupInstanceName, tgtGroupInstanceName, relationName): + """Make SMI-S call to create replica for source group. + + :param conn - the connection to the ecom server + :param repServiceInstanceName - replication service + :param srcGroupInstanceName - source group instance name + :param tgtGroupInstanceName - target group instance name + :param cgName - target group name + + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + LOG.debug( + "Parameters for CreateGroupReplica: " + "replicationService: %(replicationService)s " + "RelationName: %(relationName)s " + "sourceGroup: %(srcGroup)s " + "targetGroup: %(tgtGroup)s.", + {'replicationService': replicationService, + 'relationName': relationName, + 'srcGroup': srcGroupInstanceName, + 'tgtGroup': tgtGroupInstanceName}) + # 8 for clone. + rc, job = conn.InvokeMethod( + 'CreateGroupReplica', + replicationService, + RelationshipName=relationName, + SourceGroup=srcGroupInstanceName, + TargetGroup=tgtGroupInstanceName, + SyncType=self.utils.get_num(8, '16')) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMsg = (_("Error CreateGroupReplica: " + "source: %(source)s target: %(target)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'source': srcGroupInstanceName, + 'target': tgtGroupInstanceName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMsg) + raise exception.VolumeBackendAPIException(data=exceptionMsg) + return rc, job diff --git a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py new file mode 100644 index 000000000..e160e83b6 --- /dev/null +++ b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py @@ -0,0 +1,551 @@ +# Copyright (c) 2012 - 2015 EMC Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from cinder import exception +from cinder.i18n import _, _LE +from cinder.openstack.common import log as logging +from cinder.volume.drivers.emc import emc_vmax_utils + +LOG = logging.getLogger(__name__) + +STORAGEGROUPTYPE = 4 +POSTGROUPTYPE = 3 + +EMC_ROOT = 'root/emc' +THINPROVISIONINGCOMPOSITE = 32768 +THINPROVISIONING = 5 + + +class EMCVMAXProvisionV3(object): + """Provisioning Class for SMI-S based EMC volume drivers. + + This Provisioning class is for EMC volume drivers based on SMI-S. + It supports VMAX arrays. + """ + def __init__(self, prtcl): + self.protocol = prtcl + self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl) + + def delete_volume_from_pool( + self, conn, storageConfigservice, volumeInstanceName, volumeName): + """Given the volume instance remove it from the pool. + + :param conn: connection the the ecom server + :param storageConfigservice: volume created from job + :param volumeInstanceName: the volume instance name + :param volumeName: the volume name (String) + :returns: rc -- return code + """ + startTime = time.time() + + if isinstance(volumeInstanceName, list): + theElements = volumeInstanceName + volumeName = 'Bulk Delete' + else: + theElements = [volumeInstanceName] + + rc, job = conn.InvokeMethod( + 'ReturnElementsToStoragePool', storageConfigservice, + TheElements=theElements) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Error Delete Volume: %(volumeName)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'volumeName': volumeName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod ReturnElementsToStoragePool took: " + "%(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + return rc + + def create_volume_from_sg( + self, conn, storageConfigService, volumeName, + sgInstanceName, volumeSize): + """Create the volume and associate it with a storage group. + + We use EMCCollections parameter to supply a Device Masking Group + to contain a newly created storage volume. + + :param conn: the connection information to the ecom server + :param storageConfigService: the storage configuration service + :param volumeName: the volume name (String) + :param sgInstanceName: the storage group instance name + associated with an SLO + :param volumeSize: volume size (String) + :returns: volumeDict - the volume dict + :returns: rc - return code + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'CreateOrModifyElementFromStoragePool', + storageConfigService, ElementName=volumeName, + EMCCollections=[sgInstanceName], + ElementType=self.utils.get_num(THINPROVISIONING, '16'), + Size=self.utils.get_num(volumeSize, '64')) + + LOG.debug("Create Volume: %(volumename)s. Return code: %(rc)lu.", + {'volumename': volumeName, + 'rc': rc}) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Error Create Volume: %(volumeName)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'volumeName': volumeName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod CreateOrModifyElementFromStoragePool " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + # Find the newly created volume. + volumeDict = self.get_volume_dict_from_job(conn, job['Job']) + return volumeDict, rc + + def _find_new_storage_group( + self, conn, maskingGroupDict, storageGroupName): + """After creating an new storage group find it and return it. + + :param conn: connection to the ecom server + :param maskingGroupDict: the maskingGroupDict dict + :param storageGroupName: storage group name (String) + :returns: maskingGroupDict['MaskingGroup'] or None + """ + foundStorageGroupInstanceName = None + if 'MaskingGroup' in maskingGroupDict: + foundStorageGroupInstanceName = maskingGroupDict['MaskingGroup'] + + return foundStorageGroupInstanceName + + def get_volume_dict_from_job(self, conn, jobInstance): + """Given the jobInstance determine the volume Instance. + + :param conn: the ecom connection + :param jobInstance: the instance of a job + :returns: volumeDict - an instance of a volume + """ + associators = conn.Associators( + jobInstance, + ResultClass='EMC_StorageVolume') + volpath = associators[0].path + volumeDict = {} + volumeDict['classname'] = volpath.classname + keys = {} + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + volumeDict['keybindings'] = keys + + return volumeDict + + def create_element_replica( + self, conn, repServiceInstanceName, + cloneName, syncType, sourceInstance, targetInstance=None): + """Make SMI-S call to create replica for source element. + + :param conn - the connection to the ecom server + :param repServiceInstanceName - replication service + :param cloneName - clone volume name + :param syncType - 7: snapshot, 8: clone + :param sourceInstance - source volume instance + :param targetInstance - target volume instance + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + startTime = time.time() + + if targetInstance is None: + LOG.debug("Create targetless replica: %(clone)s " + "syncType: %(syncType)s Source: %(source)s.", + {'clone': cloneName, + 'syncType': syncType, + 'source': sourceInstance.path}) + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, SyncType=syncType, + SourceElement=sourceInstance.path) + else: + LOG.debug( + "Create replica: %(clone)s syncType: %(syncType)s " + "Source: %(source)s target: %(target)s.", + {'clone': cloneName, + 'syncType': syncType, + 'source': sourceInstance.path, + 'target': targetInstance.path}) + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, SyncType=syncType, + SourceElement=sourceInstance.path, + TargetElement=targetInstance.path) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Error Create Cloned Volume: %(cloneName)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'cloneName': cloneName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod CreateElementReplica " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + return rc, job + + def break_replication_relationship( + self, conn, repServiceInstanceName, syncInstanceName, + operation, force=False): + """Deletes the relationship between the clone/snap and source volume. + + Makes an SMI-S call to break clone relationship between the clone + volume and the source. + + :param conn: the connection to the ecom server + :param repServiceInstanceName: instance name of the replication service + :param syncInstanceName: instance name of the + SE_StorageSynchronized_SV_SV object + :param operation: operation code + :param force: force to break replication relationship if True + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + LOG.debug("Break replication relationship: %(sv)s " + "operation: %(operation)s.", + {'sv': syncInstanceName, 'operation': operation}) + + return self._modify_replica_synchronization( + conn, repServiceInstanceName, syncInstanceName, operation, force) + + def create_storage_group_v3(self, conn, controllerConfigService, + groupName, srp, slo, workload): + """Create the volume in the specified pool. + + :param conn: the connection information to the ecom server + :param controllerConfigService: the controller configuration service + :param groupName: the group name (String) + :param srp: the SRP (String) + :param slo: the SLO (String) + :param workload: the workload (String) + :returns: storageGroupInstanceName - storage group instance name + + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'CreateGroup', + controllerConfigService, + GroupName=groupName, + Type=self.utils.get_num(4, '16'), + EMCSRP=srp, + EMCSLO=slo, + EMCWorkload=workload) + + if rc != 0L: + rc, errordesc = rc, errordesc = self.utils.wait_for_job_complete( + conn, job) + if rc != 0L: + LOG.error(_LE( + "Error Create Group: %(groupName)s. " + "Return code: %(rc)lu. Error: %(error)s."), + {'groupName': groupName, + 'rc': rc, + 'error': errordesc}) + raise + + LOG.debug("InvokeMethod CreateGroup " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + foundStorageGroupInstanceName = self._find_new_storage_group( + conn, job, groupName) + + return foundStorageGroupInstanceName + + def _get_storage_pool_capability(self, conn, poolInstanceName): + """Get the pool capability. + + :param conn: the connection information to the ecom server + :param poolInstanceName: the pool instance + :returns: storagePoolCapability - the storage pool capability instance + None - if not found + """ + storagePoolCapability = None + + associators = ( + conn.AssociatorNames(poolInstanceName, + ResultClass='Symm_StoragePoolCapabilities')) + + if len(associators) > 0: + storagePoolCapability = associators[0] + + return storagePoolCapability + + def _get_storage_pool_setting( + self, conn, storagePoolCapability, slo, workload): + """Get the pool setting for pool capability. + + :param conn: the connection information to the ecom server + :param storagePoolCapability: the storage pool capability instance + :param slo: the slo string e.g Bronze + :param workload: the workload string e.g DSS_REP + :returns: foundStoragePoolSetting - the storage pool setting instance + """ + + foundStoragePoolSetting = None + storagePoolSettings = ( + conn.AssociatorNames(storagePoolCapability, + ResultClass='CIM_storageSetting')) + + for storagePoolSetting in storagePoolSettings: + settingInstanceID = storagePoolSetting['InstanceID'] + matchString = slo + ':' + workload + if matchString in settingInstanceID: + foundStoragePoolSetting = storagePoolSetting + break + return foundStoragePoolSetting + + def _get_supported_size_range_for_SLO( + self, conn, storageConfigService, + srpPoolInstanceName, storagePoolSettingInstanceName): + """Gets available performance capacity per SLO. + + :param conn: the connection information to the ecom server + :param storageConfigService: the storage configuration service instance + :param srpPoolInstanceName: the SRP storage pool instance + :param storagePoolSettingInstanceName: the SLO type + e.g Bronze + :returns: supportedSizeDict - the supported size dict + """ + startTime = time.time() + + rc, supportedSizeDict = conn.InvokeMethod( + 'GetSupportedSizeRange', + srpPoolInstanceName, + ElementType=self.utils.get_num(3, '16'), + Goal=storagePoolSettingInstanceName) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete( + conn, supportedSizeDict) + if rc != 0L: + exceptionMessage = (_( + "Cannot get supported size range for %(sps)s " + "Return code: %(rc)lu. Error: %(error)s.") + % {'sps': storagePoolSettingInstanceName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod GetSupportedSizeRange " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + return supportedSizeDict + + def get_volume_range( + self, conn, storageConfigService, poolInstanceName, slo, workload): + """Get upper and lower range for volume for slo/workload combination. + + :param conn: the connection information to the ecom server + :param storageConfigService: the storage config service + :param poolInstanceName: the pool instance + :param slo: slo string e.g Bronze + :param workload: workload string e.g DSS + :returns: maximumVolumeSize - the maximum volume size supported + :returns: minimumVolumeSize - the minimum volume size supported + """ + maximumVolumeSize = None + minimumVolumeSize = None + + storagePoolCapabilityInstanceName = self._get_storage_pool_capability( + conn, poolInstanceName) + if storagePoolCapabilityInstanceName: + storagePoolSettingInstanceName = self._get_storage_pool_setting( + conn, storagePoolCapabilityInstanceName, slo, workload) + if storagePoolCapabilityInstanceName: + supportedSizeDict = self._get_supported_size_range_for_SLO( + conn, storageConfigService, poolInstanceName, + storagePoolSettingInstanceName) + + maximumVolumeSize = supportedSizeDict['MaximumVolumeSize'] + minimumVolumeSize = supportedSizeDict['MinimumVolumeSize'] + + return maximumVolumeSize, minimumVolumeSize + + def activate_snap_relationship( + self, conn, repServiceInstanceName, syncInstanceName): + """Activate snap relationship and start copy operation. + + :param conn: the connection to the ecom server + :param repServiceInstanceName: instance name of the replication service + :param syncInstanceName: instance name of the + SE_StorageSynchronized_SV_SV object + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + # Operation 4: activate the snapVx. + operation = self.utils.get_num(4, '16') + + LOG.debug("Activate snap: %(sv)s operation: %(operation)s.", + {'sv': syncInstanceName, 'operation': operation}) + + return self._modify_replica_synchronization( + conn, repServiceInstanceName, syncInstanceName, operation) + + def return_to_resource_pool(self, conn, repServiceInstanceName, + syncInstanceName): + """Return the snap target resources back to the pool. + + :param conn: the connection to the ecom server + :param repServiceInstanceName: instance name of the replication service + :param syncInstanceName: instance name of the + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + # Operation 4: activate the snapVx. + operation = self.utils.get_num(19, '16') + + LOG.debug("Return snap resource back to pool: " + "%(sv)s operation: %(operation)s.", + {'sv': syncInstanceName, 'operation': operation}) + + return self._modify_replica_synchronization( + conn, repServiceInstanceName, syncInstanceName, operation) + + def _modify_replica_synchronization( + self, conn, repServiceInstanceName, syncInstanceName, + operation, force=False): + """Modify the relationship between the clone/snap and source volume. + + Helper function that makes an SMI-S call to break clone relationship + between the clone volume and the source. + + :param conn: the connection to the ecom server + :param repServiceInstanceName: instance name of the replication service + :param syncInstanceName: instance name of the + SE_StorageSynchronized_SV_SV object + :param operatoin: opeation code + :param force: force to modify replication synchronization if True + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + startTime = time.time() + + rc, job = conn.InvokeMethod( + 'ModifyReplicaSynchronization', repServiceInstanceName, + Operation=operation, + Synchronization=syncInstanceName, + Force=force) + + LOG.debug("_modify_replica_synchronization: %(sv)s " + "operation: %(operation)s Return code: %(rc)lu.", + {'sv': syncInstanceName, 'operation': operation, 'rc': rc}) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMessage = (_( + "Error modify replica synchronization: %(sv)s " + "operation: %(operation)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'sv': syncInstanceName, 'operation': operation, + 'rc': rc, 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("InvokeMethod ModifyReplicaSynchronization " + "took: %(delta)s H:MM:SS.", + {'delta': self.utils.get_time_delta(startTime, + time.time())}) + + return rc, job + + def create_group_replica( + self, conn, replicationService, + srcGroupInstanceName, tgtGroupInstanceName, relationName): + """Make SMI-S call to create replica for source group. + + :param conn - the connection to the ecom server + :param replicationService - replication service + :param srcGroupInstanceName - source group instance name + :param tgtGroupInstanceName - target group instance name + :param relationName - replica relationship name + :returns: rc - return code + :returns: job - job object of the replica creation operation + """ + LOG.debug( + "Creating CreateGroupReplica V3: " + "replicationService: %(replicationService)s " + "RelationName: %(relationName)s " + "sourceGroup: %(srcGroup)s " + "targetGroup: %(tgtGroup)s.", + {'replicationService': replicationService, + 'relationName': relationName, + 'srcGroup': srcGroupInstanceName, + 'tgtGroup': tgtGroupInstanceName}) + # 7 for snap. + syncType = 7 + rc, job = conn.InvokeMethod( + 'CreateGroupReplica', + replicationService, + RelationshipName=relationName, + SourceGroup=srcGroupInstanceName, + TargetGroup=tgtGroupInstanceName, + SyncType=self.utils.get_num(syncType, '16')) + + if rc != 0L: + rc, errordesc = self.utils.wait_for_job_complete(conn, job) + if rc != 0L: + exceptionMsg = (_("Error CreateGroupReplica: " + "source: %(source)s target: %(target)s. " + "Return code: %(rc)lu. Error: %(error)s.") + % {'source': srcGroupInstanceName, + 'target': tgtGroupInstanceName, + 'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMsg) + raise exception.VolumeBackendAPIException(data=exceptionMsg) + return rc, job diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index a032e9d37..bc81d7ef6 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2014 EMC Corporation. +# Copyright (c) 2012 - 2015 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import random import re from xml.dom.minidom import parseString @@ -21,7 +22,7 @@ import six from cinder import context from cinder import exception -from cinder.i18n import _, _LE, _LI +from cinder.i18n import _, _LE, _LI, _LW from cinder.openstack.common import log as logging from cinder.openstack.common import loopingcall from cinder.volume import volume_types @@ -59,12 +60,13 @@ class EMCVMAXUtils(object): def __init__(self, prtcl): if not pywbemAvailable: LOG.info(_LI( - 'Module PyWBEM not installed. ' - 'Install PyWBEM using the python-pywbem package.')) + "Module PyWBEM not installed. " + "Install PyWBEM using the python-pywbem package.")) self.protocol = prtcl def find_storage_configuration_service(self, conn, storageSystemName): - """Given the storage system name, get the storage configuration service. + """Given the storage system name, get the storage configuration + service. :param conn: connection to the ecom server :param storageSystemName: the storage system name @@ -77,13 +79,13 @@ class EMCVMAXUtils(object): if storageSystemName == configservice['SystemName']: foundConfigService = configservice LOG.debug("Found Storage Configuration Service: " - "%(configservice)s" - % {'configservice': configservice}) + "%(configservice)s.", + {'configservice': configservice}) break if foundConfigService is None: exceptionMessage = (_("Storage Configuration Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -107,13 +109,13 @@ class EMCVMAXUtils(object): if storageSystemName == configservice['SystemName']: foundConfigService = configservice LOG.debug("Found Controller Configuration Service: " - "%(configservice)s" - % {'configservice': configservice}) + "%(configservice)s.", + {'configservice': configservice}) break if foundConfigService is None: exceptionMessage = (_("Controller Configuration Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -134,13 +136,13 @@ class EMCVMAXUtils(object): if storageSystemName == elementCompositionService['SystemName']: foundElementCompositionService = elementCompositionService LOG.debug("Found Element Composition Service:" - "%(elementCompositionService)s" + "%(elementCompositionService)s." % {'elementCompositionService': elementCompositionService}) break if foundElementCompositionService is None: exceptionMessage = (_("Element Composition Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -160,15 +162,15 @@ class EMCVMAXUtils(object): for storageRelocationService in storageRelocationServices: if storageSystemName == storageRelocationService['SystemName']: foundStorageRelocationService = storageRelocationService - LOG.debug("Found Element Composition Service:" - "%(storageRelocationService)s" - % {'storageRelocationService': - storageRelocationService}) + LOG.debug( + "Found Element Composition Service: " + "%(storageRelocationService)s.", + {'storageRelocationService': storageRelocationService}) break if foundStorageRelocationService is None: exceptionMessage = (_("Storage Relocation Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -189,13 +191,13 @@ class EMCVMAXUtils(object): if storageSystemName == storageHardwareservice['SystemName']: foundHardwareService = storageHardwareservice LOG.debug("Found Storage Hardware ID Management Service:" - "%(storageHardwareservice)s" - % {'storageHardwareservice': storageHardwareservice}) + "%(storageHardwareservice)s.", + {'storageHardwareservice': storageHardwareservice}) break if foundHardwareService is None: exceptionMessage = (_("Storage HardwareId mgmt Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -216,12 +218,12 @@ class EMCVMAXUtils(object): if storageSystemName == repservice['SystemName']: foundRepService = repservice LOG.debug("Found Replication Service:" - "%(repservice)s" - % {'repservice': repservice}) + "%(repservice)s", + {'repservice': repservice}) break if foundRepService is None: exceptionMessage = (_("Replication Service not found " - "on %(storageSystemName)s") + "on %(storageSystemName)s.") % {'storageSystemName': storageSystemName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -250,7 +252,7 @@ class EMCVMAXUtils(object): if foundTierPolicyService is None: exceptionMessage = (_( "Tier Policy Service not found " - "for %(storageSystemName)s") + "for %(storageSystemName)s.") % {'storageSystemName': storageSystemInstanceName}) LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) @@ -272,22 +274,23 @@ class EMCVMAXUtils(object): LocalOnly=False) rc = jobinstance['ErrorCode'] errorDesc = jobinstance['ErrorDescription'] - LOG.debug('Return code is: %(rc)lu' - 'Error Description is: %(errorDesc)s' - % {'rc': rc, - 'errorDesc': errorDesc}) + LOG.debug("Return code is: %(rc)lu " + "Error Description is: %(errorDesc)s.", + {'rc': rc, + 'errorDesc': errorDesc}) return rc, errorDesc def _wait_for_job_complete(self, conn, job): """Given the job wait for it to complete. + Called at an interval until the job is finished. + :param conn: connection to the ecom server :param job: the job dict """ def _wait_for_job_complete(): - """Called at an interval until the job is finished""" retries = kwargs['retries'] wait_for_job_called = kwargs['wait_for_job_called'] if self._is_job_finished(conn, job): @@ -317,6 +320,7 @@ class EMCVMAXUtils(object): def _is_job_finished(self, conn, job): """Check if the job is finished. + :param conn: connection to the ecom server :param job: the job dict @@ -334,9 +338,6 @@ class EMCVMAXUtils(object): # Values("New, Starting, Running, Suspended, Shutting Down, # Completed, Terminated, Killed, Exception, Service, # Query Pending, DMTF Reserved, Vendor Reserved")] - # NOTE(deva): string matching based on - # http://ipmitool.cvs.sourceforge.net/ - # viewvc/ipmitool/ipmitool/lib/ipmi_chassis.c if jobstate in [2L, 3L, 4L, 32767L]: return False else: @@ -345,12 +346,13 @@ class EMCVMAXUtils(object): def wait_for_sync(self, conn, syncName): """Given the sync name wait for it to fully synchronize. + Called at an interval until the synchronization is finished. + :param conn: connection to the ecom server :param syncName: the syncName """ def _wait_for_sync(): - """Called at an interval until the synchronization is finished.""" retries = kwargs['retries'] wait_for_sync_called = kwargs['wait_for_sync_called'] if self._is_sync_complete(conn, syncName): @@ -379,6 +381,7 @@ class EMCVMAXUtils(object): def _is_sync_complete(self, conn, syncName): """Check if the job is finished. + :param conn: connection to the ecom server :param syncName: the sync name @@ -388,6 +391,9 @@ class EMCVMAXUtils(object): LocalOnly=False) percentSynced = syncInstance['PercentSynced'] + LOG.debug("Percent synced is %(percentSynced)lu.", + {'percentSynced': percentSynced}) + if percentSynced < 100: return False else: @@ -432,8 +438,7 @@ class EMCVMAXUtils(object): if len(groups) > 0: foundStorageSystemInstanceName = groups[0] else: - exception_message = (_("Cannot get storage system")) - LOG.error(exception_message) + LOG.error(_LE("Cannot get storage system.")) raise return foundStorageSystemInstanceName @@ -442,7 +447,7 @@ class EMCVMAXUtils(object): """Returns the storage group for a particular volume. Given the volume instance name get the associated storage group if it - is belong to one + is belong to one. :param conn: connection to the ecom server :param volumeInstanceName: the volume instance name @@ -460,6 +465,10 @@ class EMCVMAXUtils(object): return foundStorageGroupInstanceName + def wrap_get_storage_group_from_volume(self, conn, volumeInstanceName): + """Unit test aid""" + return self.get_storage_group_from_volume(conn, volumeInstanceName) + def find_storage_masking_group(self, conn, controllerConfigService, storageGroupName): """Given the storage group name get the storage group. @@ -470,6 +479,7 @@ class EMCVMAXUtils(object): :param foundStorageGroup: storage group instance name """ foundStorageMaskingGroupInstanceName = None + storageMaskingGroupInstances = ( conn.Associators(controllerConfigService, ResultClass='CIM_DeviceMaskingGroup')) @@ -487,6 +497,7 @@ class EMCVMAXUtils(object): else: foundStorageMaskingGroupInstanceName = ( storageMaskingGroupInstance.path) + break return foundStorageMaskingGroupInstanceName @@ -511,13 +522,13 @@ class EMCVMAXUtils(object): foundVolumeInstance = conn.GetInstance(volumeInstanceName) if foundVolumeInstance is None: - LOG.debug("Volume %(volumeName)s not found on the array." - % {'volumeName': volumeName}) + LOG.debug("Volume %(volumeName)s not found on the array.", + {'volumeName': volumeName}) else: LOG.debug("Volume name: %(volumeName)s Volume instance: " - "%(vol_instance)s." - % {'volumeName': volumeName, - 'vol_instance': foundVolumeInstance.path}) + "%(vol_instance)s.", + {'volumeName': volumeName, + 'vol_instance': foundVolumeInstance.path}) return foundVolumeInstance @@ -544,9 +555,6 @@ class EMCVMAXUtils(object): def get_instance_name(self, classname, bindings): """Get the instance from the classname and bindings. - NOTE: This exists in common too...will be moving it to other file - where both common and masking can access it - :param classname: class name for the volume instance :param bindings: volume created from job :returns: foundVolumeInstance - the volume instance @@ -570,11 +578,10 @@ class EMCVMAXUtils(object): :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", + LOG.debug("Ecom IP: %(ecomIp)s Port: %(ecomPort)s.", {'ecomIp': ecomIp, 'ecomPort': ecomPort}) return ecomIp, ecomPort else: @@ -588,7 +595,6 @@ class EMCVMAXUtils(object): :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: @@ -597,11 +603,32 @@ class EMCVMAXUtils(object): 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: ecomUser - the ecom user + :returns: ecomPasswd - the ecom password + """ + 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 portGroups and choose - one 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: portGroupName - the name of the port group chosen @@ -611,35 +638,40 @@ class EMCVMAXUtils(object): data = myFile.read() myFile.close() dom = parseString(data) - portGroups = dom.getElementsByTagName('PortGroups') - if portGroups is not None and len(portGroups) > 0: - portGroupsXml = portGroups[0].toxml() - portGroupsXml = portGroupsXml.replace('', '') - portGroupsXml = portGroupsXml.replace('', '') - portGroupsXml = portGroupsXml.replace('', '|') - portGroupsXml = portGroupsXml.replace('', '') - portGroupsXml = portGroupsXml.replace('\n', '') - portGroupsXml = portGroupsXml.replace('\t', '') - portGroupsXml = portGroupsXml[1:] - # convert the | separated string to a list - portGroupNames = ( - [s.strip() for s in portGroupsXml.split('|') if s]) - + 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) - - portGroupName = ( - portGroupNames[random.randint(0, numPortGroups - 1)]) - - return portGroupName - else: - exception_message = (_("Port Group name not found.")) - LOG.error(exception_message) - raise exception.VolumeBackendAPIException(data=exception_message) + 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. + Remove newlines, tabs and trailing spaces. :param fileName: the path and name of the file :returns: retString - the returned string @@ -654,6 +686,7 @@ class EMCVMAXUtils(object): 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() @@ -670,12 +703,13 @@ class EMCVMAXUtils(object): fastPolicyName = self._parse_from_file(fileName, 'FastPolicy') if fastPolicyName: - LOG.debug("File %(fileName)s: Fast Policy is %(fastPolicyName)s" - % {'fileName': fileName, - 'fastPolicyName': 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 fastPolicyName + return None def parse_array_name_from_file(self, fileName): """Parse the array name from config file. @@ -687,9 +721,11 @@ class EMCVMAXUtils(object): :returns: arrayName - the array name """ arrayName = self._parse_from_file(fileName, 'Array') - if not arrayName: + if arrayName: + return arrayName + else: LOG.debug("Array not found from config file.") - return arrayName + return None def parse_pool_name_from_file(self, fileName): """Parse the pool name from config file. @@ -700,10 +736,43 @@ class EMCVMAXUtils(object): :returns: poolName - the pool name """ poolName = self._parse_from_file(fileName, 'Pool') - if not poolName: + 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. - return poolName + Please note that the string 'NONE' is returned if it is not found. + + :param fileName: the path and name of the file + :returns: slo - 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: workload - 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_pool_instance_id(self, poolInstanceId): """Given the instance Id parse the pool name and system name from it. @@ -724,8 +793,31 @@ class EMCVMAXUtils(object): if len(idarray) > 2: systemName = idarray[0] + '+' + idarray[1] - LOG.debug("Pool name: %(poolName)s System name: %(systemName)s." - % {'poolName': poolName, 'systemName': systemName}) + LOG.debug("Pool name: %(poolName)s System name: %(systemName)s.", + {'poolName': poolName, 'systemName': systemName}) + return poolName, systemName + + def parse_pool_instance_id_v3(self, poolInstanceId): + """Given the instance Id parse the pool name and system name from it. + + Example of pool InstanceId: Symmetrix+0001233455555+U+Pool 0 + + :param poolInstanceId: the path and name of the file + :returns: poolName - the pool name + :returns: systemName - the system name + """ + poolName = None + systemName = None + endp = poolInstanceId.rfind('-+-') + if endp > -1: + poolName = poolInstanceId[endp + 3:] + + idarray = poolInstanceId.split('-+-') + if len(idarray) > 2: + systemName = idarray[0] + '-+-' + idarray[1] + + LOG.debug("Pool name: %(poolName)s System name: %(systemName)s.", + {'poolName': poolName, 'systemName': systemName}) return poolName, systemName def convert_gb_to_bits(self, strGbSize): @@ -736,8 +828,8 @@ class EMCVMAXUtils(object): """ strBitsSize = six.text_type(int(strGbSize) * 1024 * 1024 * 1024) - LOG.debug("Converted %(strGbSize)s GBs to %(strBitsSize)s Bits" - % {'strGbSize': strGbSize, 'strBitsSize': strBitsSize}) + LOG.debug("Converted %(strGbSize)s GBs to %(strBitsSize)s Bits.", + {'strGbSize': strGbSize, 'strBitsSize': strBitsSize}) return strBitsSize @@ -885,9 +977,7 @@ class EMCVMAXUtils(object): return foundSpaceConsumed def get_volume_size(self, conn, volumeInstance): - """Get the volume size. - - ConsumableBlocks * BlockSize + """Get the volume size. ConsumableBlocks * BlockSize. :param conn: the connection information to the ecom server :param volumeInstance: the volume Instance @@ -947,7 +1037,7 @@ class EMCVMAXUtils(object): """Gets the extra specs associated with a volume type. Given the string value of the volume type name, get the extra specs - object associated with the volume type + object associated with the volume type. :param volumeTypeName: string value of the volume type name :returns: extra_specs - extra specs object @@ -962,7 +1052,7 @@ class EMCVMAXUtils(object): """Get the total and remaining capacity in GB for a storage pool. Given the storage pool name, get the total capacity and remaining - capacity in GB + capacity in GB. :param conn: connection to the ecom server :param storagePoolName: string value of the storage pool name @@ -970,18 +1060,18 @@ class EMCVMAXUtils(object): :returns: free_capacity_gb - remaining capacity of the storage pool in GB """ - LOG.debug("Retrieving capacity for pool %(poolName)s on array " - "%(array)s" - % {'poolName': poolName, - 'array': storageSystemName}) + LOG.debug( + "Retrieving capacity for pool %(poolName)s on array %(array)s.", + {'poolName': poolName, + 'array': storageSystemName}) poolInstanceName = self.get_pool_by_name( conn, poolName, storageSystemName) if poolInstanceName is None: - LOG.error("Unable to retrieve pool instance of %(poolName)s on " - "array %(array)s" - % {'poolName': poolName, - 'array': storageSystemName}) + LOG.error(_LE( + "Unable to retrieve pool instance of %(poolName)s on " + "array %(array)s."), + {'poolName': poolName, 'array': storageSystemName}) return (0, 0) storagePoolInstance = conn.GetInstance( poolInstanceName, LocalOnly=False) @@ -1001,9 +1091,9 @@ class EMCVMAXUtils(object): :returns: poolInstanceName - instance name of storage pool """ foundPoolInstanceName = None - LOG.debug("storagePoolName: %(poolName)s, storageSystemName: %(array)s" - % {'poolName': storagePoolName, - 'array': storageSystemName}) + LOG.debug( + "storagePoolName: %(poolName)s, storageSystemName: %(array)s.", + {'poolName': storagePoolName, 'array': storageSystemName}) poolInstanceNames = conn.EnumerateInstanceNames( 'EMC_VirtualProvisioningPool') for poolInstanceName in poolInstanceNames: @@ -1042,7 +1132,7 @@ class EMCVMAXUtils(object): return size1GBs - size2GBs - def get_volumetype_extraspecs(self, volume): + def get_volumetype_extraspecs(self, volume, volumeTypeId=None): """Compare the bit sizes to an approximate. :param volume: the volume dictionary @@ -1051,7 +1141,10 @@ class EMCVMAXUtils(object): extraSpecs = {} try: - type_id = volume['volume_type_id'] + if volumeTypeId: + type_id = volumeTypeId + else: + type_id = volume['volume_type_id'] if type_id is not None: extraSpecs = volume_types.get_volume_type_extra_specs(type_id) @@ -1130,7 +1223,7 @@ class EMCVMAXUtils(object): return 'Undetermined' def get_short_protocol_type(self, protocol): - """Given the protocol type, return I for iscsi and F for fc + """Given the protocol type, return I for iscsi and F for fc. :param protocol: iscsi or fc :returns: 'I' or 'F' @@ -1148,7 +1241,8 @@ class EMCVMAXUtils(object): :param conn: connection to the ecom server :param: hardwareIdManagementService - hardware id management service - :returns: hardwareIdInstances - the list of hardware id instances + :returns: hardwareIdInstances - the list of hardware + id instances """ hardwareIdInstances = ( conn.Associators(hardwareIdManagementService, @@ -1156,36 +1250,406 @@ class EMCVMAXUtils(object): return hardwareIdInstances - def find_ip_protocol_endpoint(self, conn, storageSystemName): - """Find the IP protocol endpoint for ISCSI. + def truncate_string(self, strToTruncate, maxNum): + """Truncate a string by taking first and last characters. + + :param strToTruncate: the string to be truncated + :param maxNum: the maximum number of characters + :returns: truncated string or original string + """ + if len(strToTruncate) > maxNum: + newNum = len(strToTruncate) - maxNum / 2 + firstChars = strToTruncate[:maxNum / 2] + lastChars = strToTruncate[newNum:] + strToTruncate = firstChars + lastChars + + return strToTruncate + + def get_array(self, host): + """Extract the array from the host capabilites. + + :param host: the host object + :returns: storageSystem - storage system represents the array + """ + + try: + if '@' in host: + infoDetail = host.split('@') + storageSystem = 'SYMMETRIX+' + infoDetail[0] + except Exception: + LOG.error(_LE("Error parsing array from host capabilities.")) + + return storageSystem + + def get_time_delta(self, startTime, endTime): + """Get the delta between start and end time. + + :param startTime: the start time + :param endTime: the end time + :returns: delta in string H:MM:SS + """ + delta = endTime - startTime + return str(datetime.timedelta(seconds=int(delta))) + + def find_sync_sv_by_target( + self, conn, storageSystem, target, waitforsync=True): + """Find the storage synchronized name by target device ID. + + :param conn: connection to the ecom server + :param storageSystem: the storage system name + :param target: target volume object + :param waitforsync: wait for the synchronization to complete if True + :returns: foundSyncName (String) + """ + foundSyncInstanceName = None + syncInstanceNames = conn.EnumerateInstanceNames( + 'SE_StorageSynchronized_SV_SV') + for syncInstanceName in syncInstanceNames: + syncSvTarget = syncInstanceName['SyncedElement'] + if storageSystem != syncSvTarget['SystemName']: + continue + if syncSvTarget['DeviceID'] == target['DeviceID']: + # Check that it hasn't recently been deleted. + try: + conn.GetInstance(syncInstanceName) + foundSyncInstanceName = syncInstanceName + LOG.debug("Found sync Name: " + "%(syncName)s.", + {'syncName': foundSyncInstanceName}) + except Exception: + foundSyncInstanceName = None + break + + if foundSyncInstanceName is None: + LOG.warning(_LW( + "Storage sync name not found for target %(target)s " + "on %(storageSystem)s."), + {'target': target['DeviceID'], 'storageSystem': storageSystem}) + else: + # Wait for SE_StorageSynchronized_SV_SV to be fully synced. + if waitforsync: + self.wait_for_sync(conn, foundSyncInstanceName) + + return foundSyncInstanceName + + def find_group_sync_rg_by_target( + self, conn, storageSystem, targetRgInstanceName, waitforsync=True): + """Find the SE_GroupSynchronized_RG_RG instance name by target group. + + :param conn: connection to the ecom server + :param storageSystem: the storage system name + :param targetRgInstanceName: target group instance name + :param waitforsync: wait for synchronization to complete + :returns: foundSyncInstanceName + """ + foundSyncInstanceName = None + groupSyncRgInstanceNames = conn.EnumerateInstanceNames( + 'SE_GroupSynchronized_RG_RG') + for rgInstanceName in groupSyncRgInstanceNames: + rgTarget = rgInstanceName['SyncedElement'] + if targetRgInstanceName['InstanceID'] == rgTarget['InstanceID']: + # Check that it has not recently been deleted. + try: + conn.GetInstance(rgInstanceName) + foundSyncInstanceName = rgInstanceName + LOG.debug("Found group sync name: " + "%(syncName)s.", + {'syncName': foundSyncInstanceName}) + except Exception: + foundSyncInstanceName = None + break + + if foundSyncInstanceName is None: + LOG.warning(_LW( + "Group sync name not found for target group %(target)s " + "on %(storageSystem)s."), + {'target': targetRgInstanceName['InstanceID'], + 'storageSystem': storageSystem}) + else: + # Wait for SE_StorageSynchronized_SV_SV to be fully synced. + if waitforsync: + self.wait_for_sync(conn, foundSyncInstanceName) + + return foundSyncInstanceName + + def populate_cgsnapshot_status( + self, context, db, cgsnapshot_id, status='available'): + """Update cgsnapshot status in the cinder database. + + :param context: + :param db: cinder database + :param cgsnapshot_id: cgsnapshot id + :param status: string value reflects the status of the member snapshot + :return snapshots: updated snapshots + """ + snapshots = db.snapshot_get_all_for_cgsnapshot(context, cgsnapshot_id) + LOG.info(_LI( + "Populating status for cgsnapshot: %(id)s."), + {'id': cgsnapshot_id}) + if snapshots: + for snapshot in snapshots: + snapshot['status'] = status + else: + LOG.info(_LI("No snapshot found for %(cgsnapshot)s."), + {'cgsnapshot': cgsnapshot_id}) + return snapshots + + def get_firmware_version(self, conn, arrayName): + """Get the firmware version of array. :param conn: the connection to the ecom server - :param storageSystemName: the storage system name - :returns: foundIpAddress - """ - foundIpAddress = None - ipProtocolEndpointInstances = conn.EnumerateInstances( - 'CIM_IPProtocolEndpoint') - - for ipProtocolEndpointInstance in ipProtocolEndpointInstances: - ipStorageSystemName = ( - ipProtocolEndpointInstance.path['SystemName']) - if storageSystemName in ipStorageSystemName: - propertiesList = ( - ipProtocolEndpointInstance.properties.items()) + :param arrayName: the array name + :returns: firmwareVersion (String) + """ + firmwareVersion = None + softwareIdentities = conn.EnumerateInstanceNames( + 'symm_storageSystemsoftwareidentity') + + for softwareIdentity in softwareIdentities: + if arrayName in softwareIdentity['InstanceID']: + softwareIdentityInstance = conn.GetInstance(softwareIdentity) + propertiesList = softwareIdentityInstance.properties.items() for properties in propertiesList: - if properties[0] == 'IPv4Address': + if properties[0] == 'VersionString': cimProperties = properties[1] - foundIpAddress = cimProperties.value - if (foundIpAddress == '127.0.0.1' - or foundIpAddress == '0.0.0.0'): - foundIpAddress = None - else: - break - if foundIpAddress is not None: + firmwareVersion = cimProperties.value + break + + return firmwareVersion + + def get_srp_pool_stats(self, conn, arrayName, poolName): + """Get the totalManagedSpace, remainingManagedSpace. + + :param conn: the connection to the ecom server + :param arrayName: the array name + :param poolName: the pool name + :returns: totalManagedSpace, remainingManagedSpace + """ + totalCapacityGb = -1 + remainingCapacityGb = -1 + storageSystemInstanceName = self.find_storageSystem(conn, arrayName) + + srpPoolInstanceNames = conn.AssociatorNames( + storageSystemInstanceName, + ResultClass='Symm_SRPStoragePool') + + for srpPoolInstanceName in srpPoolInstanceNames: + poolInstanceID = srpPoolInstanceName['InstanceID'] + poolnameStr, _ = ( + self.parse_pool_instance_id_v3(poolInstanceID)) + + if six.text_type(poolName) == six.text_type(poolnameStr): + try: + # Check that pool hasnt 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.convert_bits_to_gbs( + totalManagedSpace) + elif properties[0] == 'RemainingManagedSpace': + cimProperties = properties[1] + remainingManagedSpace = cimProperties.value + remainingCapacityGb = self.convert_bits_to_gbs( + remainingManagedSpace) + except Exception: + pass + + return totalCapacityGb, remainingCapacityGb + + def isArrayV3(self, conn, arrayName): + """Check is the array is V2 or V3. + + :param conn: the connection to the ecom server + :param arrayName: the array name + :returns: boolean + """ + firmwareVersion = self.get_firmware_version(conn, arrayName) + + m = re.search('^(\d+)', firmwareVersion) + majorVersion = m.group(0) + + if int(majorVersion) >= 5900: + return True + else: + return False + + def get_pool_and_system_name_v2( + self, conn, storageSystemInstanceName, poolNameInStr): + """Get pool instance and system name string for V2. + + :param conn: the connection to the ecom server + :param storageSystemInstanceName: the storage system instance name + :param poolNameInStr: the pool name + :returns: foundPoolInstanceName, systemNameStr + """ + foundPoolInstanceName = None + vpoolInstanceNames = conn.AssociatorNames( + storageSystemInstanceName, + ResultClass='EMC_VirtualProvisioningPool') + + for vpoolInstanceName in vpoolInstanceNames: + poolInstanceId = vpoolInstanceName['InstanceID'] + # Example: SYMMETRIX+000195900551+TP+Sol_Innov + poolnameStr, systemNameStr = self.parse_pool_instance_id( + poolInstanceId) + if poolnameStr is not None and systemNameStr is not None: + if six.text_type(poolNameInStr) == six.text_type(poolnameStr): + # check that the pool hasnt 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): + """Get pool instance and system name string for V2. + + :param conn: the connection to the ecom server + :param storageSystemInstanceName: the storage system instance name + :param poolNameInStr: the pool name + :returns: foundPoolInstanceName, systemNameStr + """ + foundPoolInstanceName = None + srpPoolInstanceNames = conn.AssociatorNames( + storageSystemInstanceName, + ResultClass='Symm_SRPStoragePool') + + for srpPoolInstanceName in srpPoolInstanceNames: + poolInstanceID = srpPoolInstanceName['InstanceID'] + # Example: SYMMETRIX-+-000196700535-+-SR-+-SRP_1 + poolnameStr, systemNameStr = self.parse_pool_instance_id_v3( + poolInstanceID) + if poolnameStr is not None and systemNameStr is not None: + if six.text_type(poolNameInStr) == six.text_type(poolnameStr): + try: + conn.GetInstance(srpPoolInstanceName) + foundPoolInstanceName = srpPoolInstanceName + except Exception: + foundPoolInstanceName = None + break + + return foundPoolInstanceName, systemNameStr + + def find_storageSystem(self, conn, arrayStr): + """Find an array instance name given the array name. + + :param arrayStr: the array Serial number (string) + :returns: foundPoolInstanceName, the CIM Instance Name of the Pool + """ + foundStorageSystemInstanceName = None + storageSystemInstanceNames = conn.EnumerateInstanceNames( + 'EMC_StorageSystem') + for storageSystemInstanceName in storageSystemInstanceNames: + arrayName = storageSystemInstanceName['Name'] + index = arrayName.find(arrayStr) + if index > -1: + foundStorageSystemInstanceName = storageSystemInstanceName + + if foundStorageSystemInstanceName is None: + exceptionMessage = (_("StorageSystem %(array)s is not found.") + % {'array': arrayStr}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + LOG.debug("Array Found: %(array)s.", + {'array': arrayStr}) + + return foundStorageSystemInstanceName + + def is_in_range(self, volumeSize, maximumVolumeSize, minimumVolumeSize): + """Check that volumeSize is in range. + + :param volumeSize: volume size + :param maximumVolumeSize: the max volume size + :param minimumVolumeSize: the min volume size + :returns: true/false + """ + + if (long(volumeSize) < long(maximumVolumeSize)) and ( + long(volumeSize) >= long(minimumVolumeSize)): + return True + else: + return False + + def verify_slo_workload(self, slo, workload): + """Check if SLO and workload values are valid. + + :param slo: Service Level Object e.g bronze + :param workload: workload e.g DSS + :returns: true/false + """ + isValidSLO = False + isValidWorkload = False + + validSLOs = ['Bronze', 'Silver', 'Gold', + 'Platinum', 'Diamond', 'Optimized', + 'NONE'] + validWorkloads = ['DSS_REP', 'DSS', 'OLTP', + 'OLTP_REP', 'NONE'] + + for validSLO in validSLOs: + if slo == validSLO: + isValidSLO = True break - return foundIpAddress + for validWorkload in validWorkloads: + if workload == validWorkload: + isValidWorkload = True + break + + if not isValidSLO: + LOG.error(_LE( + "SLO: %(slo)s is not valid. Valid values are Bronze, Silver, " + "Gold, Platinum, Diamond, Optimized, NONE."), {'slo': slo}) + + if not isValidWorkload: + LOG.error(_LE( + "Workload: %(workload)s is not valid. Valid values are " + "DSS_REP, DSS, OLTP, OLTP_REP, NONE."), {'workload': workload}) + + return isValidSLO, isValidWorkload + + def get_v3_storage_group_name(self, poolName, slo, workload): + """Determine default v3 storage group from extraSpecs. + + :param poolName: the poolName + :param slo: the SLO string e.g Bronze + :param workload: the workload string e.g DSS + :returns: storageGroupName + """ + return 'OS-' + poolName + '-' + slo + '-' + workload + '-SG' + + def strip_short_host_name(self, storageGroupName): + tempList = storageGroupName.split("-") + if len(tempList) == 6: + shorthostName = tempList.pop(1) + updatedStorageGroup = "-".join(tempList) + return updatedStorageGroup, shorthostName + else: + shorthostName = None + return storageGroupName, shorthostName + + def _get_fast_settings_from_storage_group(self, storageGroupInstance): + """Get the emc FAST setting from the storage group. + + :param storageGroupInstance: the storage group instance + :returns: emcFastSetting + """ + emcFastSetting = None + propertiesList = storageGroupInstance.properties.items() + for properties in propertiesList: + if properties[0] == 'EMCFastSetting': + cimProperties = properties[1] + emcFastSetting = cimProperties.value + break + return emcFastSetting def get_volume_meta_head(self, conn, volumeInstanceName): """Get the head of a meta volume. @@ -1201,9 +1665,9 @@ class EMCVMAXUtils(object): if len(metaHeads) > 0: metaHeadInstanceName = metaHeads[0] if metaHeadInstanceName is None: - LOG.info(_LI("Volume %(volume)s does not have meta device " - "members."), - {'volume': volumeInstanceName}) + LOG.info(_LI( + "Volume %(volume)s does not have meta device members."), + {'volume': volumeInstanceName}) return metaHeadInstanceName @@ -1218,7 +1682,7 @@ class EMCVMAXUtils(object): metaHeadInstanceName, AssocClass='CIM_BasedOn', ResultClass='EMC_PartialAllocOfConcreteExtent') - LOG.debug("metaMembers: %(members)s " % {'members': metaMembers}) + LOG.debug("metaMembers: %(members)s.", {'members': metaMembers}) return metaMembers def get_meta_members_capacity_in_bit(self, conn, volumeInstanceNames): @@ -1248,7 +1712,6 @@ class EMCVMAXUtils(object): instance = conn.GetInstance(instanceName, LocalOnly=False) except pywbem.cim_operations.CIMError as arg: instance = self.process_exception_args(arg, instanceName) - return instance def process_exception_args(self, arg, instanceName): @@ -1275,29 +1738,3 @@ class EMCVMAXUtils(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) return instance - - def find_storageSystem(self, conn, arrayStr): - """Find an array instance name given the array name. - - :param arrayStr: the array Serial number (string) - :returns: foundPoolInstanceName, the CIM Instance Name of the Pool - """ - foundStorageSystemInstanceName = None - storageSystemInstanceNames = conn.EnumerateInstanceNames( - 'EMC_StorageSystem') - for storageSystemInstanceName in storageSystemInstanceNames: - arrayName = storageSystemInstanceName['Name'] - index = arrayName.find(arrayStr) - if index > -1: - foundStorageSystemInstanceName = storageSystemInstanceName - - if foundStorageSystemInstanceName is None: - exceptionMessage = (_("StorageSystem %(array)s was not found.") - % {'array': arrayStr}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException(data=exceptionMessage) - - LOG.debug("Array Found: %(array)s." - % {'array': arrayStr}) - - return foundStorageSystemInstanceName -- 2.45.2