From 9fb28689e6e065342fcca6053ea70b73bdd9caa4 Mon Sep 17 00:00:00 2001 From: Helen Walsh Date: Sat, 13 Feb 2016 12:12:47 +0000 Subject: [PATCH] EMC VMAX - SnapVX and other snapshot improvements Using snapVX VMAX3 for creating snapshot and volume from snapshot. Improvements in wait_for_sync to determine when the max number of retries has been reached. Change-Id: Ifc5f80637ef7b86a6d3719b3a1b152556c6919a6 Closes-Bug: #1522821 --- cinder/tests/unit/test_emc_vmax.py | 95 ++++++++++- cinder/volume/drivers/emc/emc_vmax_common.py | 148 ++++++++++-------- cinder/volume/drivers/emc/emc_vmax_fc.py | 1 + cinder/volume/drivers/emc/emc_vmax_iscsi.py | 1 + .../volume/drivers/emc/emc_vmax_provision.py | 51 ++---- .../drivers/emc/emc_vmax_provision_v3.py | 136 ++++++++++++---- cinder/volume/drivers/emc/emc_vmax_utils.py | 117 ++++++++++++-- 7 files changed, 400 insertions(+), 149 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index a297145ac..7d7a8980a 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -533,7 +533,7 @@ class FakeEcomConnection(object): EMCCollections=None, InitiatorMaskingGroup=None, DeviceMaskingGroup=None, TargetMaskingGroup=None, ProtocolController=None, StorageID=None, IDType=None, - WaitForCopyState=None): + WaitForCopyState=None, Collections=None): rc = 0 myjob = SE_ConcreteJob() @@ -2331,7 +2331,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.utils._is_sync_complete = mock.Mock( return_value=True) rc = self.driver.utils.wait_for_sync(conn, mysync) - self.assertIsNone(rc) + self.assertIsNotNone(rc) self.driver.utils._is_sync_complete.assert_called_once_with( conn, mysync) self.assertTrue(self.driver.utils._is_sync_complete.return_value) @@ -2341,7 +2341,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): loopingcall_orig = loopingcall.FixedIntervalLoopingCall loopingcall.FixedIntervalLoopingCall = mock.Mock() rc = self.driver.utils.wait_for_sync(conn, mysync) - self.assertIsNone(rc) + self.assertIsNotNone(rc) loopingcall.FixedIntervalLoopingCall.assert_called_once_with( mock.ANY) loopingcall.FixedIntervalLoopingCall.reset_mock() @@ -2363,7 +2363,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.utils._is_sync_complete = mock.Mock( return_value=True) rc = self.driver.utils.wait_for_sync(conn, mysync, extraSpecs) - self.assertIsNone(rc) + self.assertIsNotNone(rc) self.driver.utils._is_sync_complete.assert_called_once_with( conn, mysync) self.assertTrue(self.driver.utils._is_sync_complete.return_value) @@ -2377,7 +2377,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): loopingcall_orig = loopingcall.FixedIntervalLoopingCall loopingcall.FixedIntervalLoopingCall = mock.Mock() rc = self.driver.utils.wait_for_sync(conn, mysync) - self.assertIsNone(rc) + self.assertIsNotNone(rc) loopingcall.FixedIntervalLoopingCall.assert_called_once_with( mock.ANY) loopingcall.FixedIntervalLoopingCall.reset_mock() @@ -7690,8 +7690,8 @@ class EMCVMAXFCTest(test.TestCase): class EMCVMAXUtilsTest(test.TestCase): def setUp(self): - self.data = EMCVMAXCommonData() + super(EMCVMAXUtilsTest, self).setUp() configuration = mock.Mock() @@ -7726,3 +7726,86 @@ class EMCVMAXUtilsTest(test.TestCase): exception.VolumeBackendAPIException, self.driver.utils.get_protocol_controller, conn, hardwareid) + + def test_set_target_element_supplier_in_rsd(self): + conn = FakeEcomConnection() + extraSpecs = self.data.extra_specs + repServiceInstanceName = ( + self.driver.utils.find_replication_service( + conn, self.data.storage_system)) + rsdInstance = self.driver.utils.set_target_element_supplier_in_rsd( + conn, repServiceInstanceName, + emc_vmax_common.SNAPVX_REPLICATION_TYPE, + emc_vmax_common.CREATE_NEW_TARGET, extraSpecs) + self.assertIsNotNone(rsdInstance) + + def test_set_copy_methodology_in_rsd(self): + conn = FakeEcomConnection() + extraSpecs = self.data.extra_specs + repServiceInstanceName = ( + self.driver.utils.find_replication_service( + conn, self.data.storage_system)) + rsdInstance = self.driver.utils.set_copy_methodology_in_rsd( + conn, repServiceInstanceName, + emc_vmax_provision.SYNC_CLONE_LOCAL, + emc_vmax_provision.COPY_ON_WRITE, extraSpecs) + self.assertIsNotNone(rsdInstance) + + +class EMCVMAXCommonTest(test.TestCase): + def setUp(self): + self.data = EMCVMAXCommonData() + + super(EMCVMAXCommonTest, self).setUp() + + configuration = mock.Mock() + configuration.safe_get.return_value = 'CommonTests' + configuration.config_group = 'CommonTests' + emc_vmax_common.EMCVMAXCommon._gather_info = mock.Mock() + driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) + driver.db = FakeDB() + self.driver = driver + self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_duplicate_volume(self, mock_pool): + common = self.driver.common + common.conn = FakeEcomConnection() + volumeInstanceName = ( + common.conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + sourceInstance = common.conn.GetInstance(volumeInstanceName) + cloneName = "SS-V3-Vol" + extraSpecs = {'volume_backend_name': 'V3_BE', + 'isV3': True, + 'storagetype:pool': 'SRP_1', + 'storagetype:workload': 'DSS', + 'storagetype:slo': 'Bronze'} + targetInstance = common.conn.GetInstance(volumeInstanceName) + common.utils.find_volume_instance = mock.Mock( + return_value=targetInstance) + duplicateVolumeInstance = self.driver.common._create_duplicate_volume( + sourceInstance, cloneName, extraSpecs) + self.assertIsNotNone(duplicateVolumeInstance) + + def test_cleanup_target(self): + common = self.driver.common + common.conn = FakeEcomConnection() + volumeInstanceName = ( + common.conn.EnumerateInstanceNames("EMC_StorageVolume")[0]) + extraSpecs = {'volume_backend_name': 'V3_BE', + 'isV3': True, + 'storagetype:pool': 'SRP_1', + 'storagetype:workload': 'DSS', + 'storagetype:slo': 'Bronze'} + targetInstance = common.conn.GetInstance(volumeInstanceName) + repServiceInstanceName = ( + self.driver.utils.find_replication_service( + common.conn, self.data.storage_system)) + common.utils.find_sync_sv_by_target = mock.Mock( + return_value=(None, None)) + + self.driver.common._cleanup_target( + repServiceInstanceName, targetInstance, extraSpecs) diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 164b3fa74..1664215c1 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -69,6 +69,10 @@ RETRIES = 'storagetype:retries' ISV3 = 'isV3' TRUNCATE_5 = 5 TRUNCATE_8 = 8 +SNAPVX = 7 +DISSOLVE_SNAPVX = 9 +CREATE_NEW_TARGET = 2 +SNAPVX_REPLICATION_TYPE = 6 emc_opts = [ cfg.StrOpt('cinder_emc_config_file', @@ -2372,6 +2376,12 @@ class EMCVMAXCommon(object): if not extraSpecs[ISV3]: snapshotInstance = self._find_lun(snapshot) + if snapshotInstance is None: + LOG.error(_LE( + "Snapshot %(snapshotname)s not found on the array. " + "No volume to delete."), + {'snapshotname': snapshotname}) + return (-1, snapshotname) storageSystem = snapshotInstance['SystemName'] # Wait for it to fully sync in case there is an ongoing @@ -2937,13 +2947,9 @@ class EMCVMAXCommon(object): :param extraSpecs: extra specifications :returns: sgInstanceName """ - 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) + storageGroupName, controllerConfigService, sgInstanceName = ( + self.utils.get_v3_default_sg_instance_name( + self.conn, poolName, slo, workload, storageSystemName)) if sgInstanceName is None: sgInstanceName = self.provisionv3.create_storage_group_v3( self.conn, controllerConfigService, storageGroupName, @@ -3784,65 +3790,43 @@ class EMCVMAXCommon(object): :returns: dict -- cloneDict """ cloneName = cloneVolume['name'] - # Default syncType 8: clone. - syncType = self.utils.get_num(8, '16') - # Default operation 8: Detach for clone. - operation = self.utils.get_num(8, '16') - - numOfBlocks = sourceInstance['NumberOfBlocks'] - blockSize = sourceInstance['BlockSize'] - volumeSizeInbits = numOfBlocks * blockSize - - volume = {'size': - int(self.utils.convert_bits_to_gbs(volumeSizeInbits))} - _rc, volumeDict, _storageSystemName = ( - self._create_v3_volume( - volume, cloneName, volumeSizeInbits, extraSpecs)) - 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}) + # SyncType 7: snap, VG3R default snapshot is snapVx. + syncType = self.utils.get_num(SNAPVX, '16') + # Operation 9: Dissolve for snapVx. + operation = self.utils.get_num(DISSOLVE_SNAPVX, '16') + rsdInstance = None + targetInstance = None if isSnapshot: - # SyncType 7: snap, VG3R default snapshot is snapVx. - syncType = self.utils.get_num(7, '16') - # Operation 9: Dissolve for snapVx. - operation = self.utils.get_num(9, '16') + rsdInstance = self.utils.set_target_element_supplier_in_rsd( + self.conn, repServiceInstanceName, SNAPVX_REPLICATION_TYPE, + CREATE_NEW_TARGET, extraSpecs) + else: + targetInstance = self._create_duplicate_volume( + sourceInstance, cloneName, extraSpecs) try: _rc, job = ( self.provisionv3.create_element_replica( self.conn, repServiceInstanceName, cloneName, syncType, - sourceInstance, extraSpecs, targetInstance)) + sourceInstance, extraSpecs, targetInstance, rsdInstance)) except Exception: LOG.warning(_LW( "Clone failed on V3. Cleaning up the target volume. " "Clone name: %(cloneName)s "), {'cloneName': cloneName}) # Check if the copy session exists. - storageSystem = targetInstance['SystemName'] - syncInstanceName = self.utils.find_sync_sv_by_target( - self.conn, storageSystem, targetInstance, False) - if syncInstanceName is not None: - # Break the clone relationship. - rc, job = self.provisionv3.break_replication_relationship( - self.conn, repServiceInstanceName, syncInstanceName, - operation, extraSpecs, True) - storageConfigService = ( - self.utils.find_storage_configuration_service( - self.conn, storageSystem)) - deviceId = targetInstance['DeviceID'] - volumeName = targetInstance['Name'] - rc = self._delete_from_pool_v3( - storageConfigService, targetInstance, volumeName, - deviceId, extraSpecs) - # Re-throw the exception. - raise + if targetInstance: + self._cleanup_target( + repServiceInstanceName, targetInstance, extraSpecs) + # Re-throw the exception. + raise cloneDict = self.provisionv3.get_volume_dict_from_job( self.conn, job['Job']) + targetVolumeInstance = ( + self.provisionv3.get_volume_from_job(self.conn, job['Job'])) + LOG.info(_LI("The target instance device id is: %(deviceid)s."), + {'deviceid': targetVolumeInstance['DeviceID']}) cloneVolume['provider_location'] = six.text_type(cloneDict) @@ -3850,21 +3834,36 @@ class EMCVMAXCommon(object): self._find_storage_sync_sv_sv(cloneVolume, sourceVolume, extraSpecs, True)) - # Detach/dissolve the clone/snap relationship. - # 8 - Detach operation. - # 9 - Dissolve operation. - if isSnapshot: - # Operation 9: 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, extraSpecs) return rc, cloneDict + def _cleanup_target( + self, repServiceInstanceName, targetInstance, extraSpecs): + """cleanup target after exception + + :param repServiceInstanceName: the replication service + :param targetInstance: the target instance + :param extraSpecs: extra specifications + """ + storageSystem = targetInstance['SystemName'] + syncInstanceName = self.utils.find_sync_sv_by_target( + self.conn, storageSystem, targetInstance, False) + if syncInstanceName is not None: + # Break the clone relationship. + self.provisionv3.break_replication_relationship( + self.conn, repServiceInstanceName, syncInstanceName, + DISSOLVE_SNAPVX, extraSpecs, True) + storageConfigService = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystem)) + deviceId = targetInstance['DeviceID'] + volumeName = targetInstance['Name'] + self._delete_from_pool_v3( + storageConfigService, targetInstance, volumeName, + deviceId, extraSpecs) + def _delete_cg_and_members( self, storageSystem, cgName, modelUpdate, volumes, extraSpecs): """Helper function to delete a consistencygroup and its member volumes. @@ -4434,3 +4433,30 @@ class EMCVMAXCommon(object): volumeName, new_size_in_bits, extraSpecs) return rc, volumeDict + + def _create_duplicate_volume( + self, sourceInstance, cloneName, extraSpecs): + """Create a volume in the same dimensions of the source volume. + + :param sourceInstance: the source volume instance + :param cloneName: the user supplied snap name + :param extraSpecs: additional info + :returns: targetInstance + """ + numOfBlocks = sourceInstance['NumberOfBlocks'] + blockSize = sourceInstance['BlockSize'] + volumeSizeInbits = numOfBlocks * blockSize + + volume = {'size': + int(self.utils.convert_bits_to_gbs(volumeSizeInbits))} + _rc, volumeDict, _storageSystemName = ( + self._create_v3_volume( + volume, cloneName, volumeSizeInbits, extraSpecs)) + 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}) + return targetInstance diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index f5f1de30b..960b09abf 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -60,6 +60,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): - Changing PercentSynced to CopyState (bug #1517103) - Getting iscsi ip from port in existing masking view - Replacement of EMCGetTargetEndpoints api (bug #1512791) + - VMAX3 snapvx improvements (bug #1522821) """ VERSION = "2.3.0" diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 66d3fc0e7..ec7ebf456 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -66,6 +66,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): - Changing PercentSynced to CopyState (bug #1517103) - Getting iscsi ip from port in existing masking view - Replacement of EMCGetTargetEndpoints api (bug #1512791) + - VMAX3 snapvx improvements (bug #1522821) """ VERSION = "2.3.0" diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 2a769a826..c38174a06 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision.py @@ -30,6 +30,9 @@ POSTGROUPTYPE = 3 EMC_ROOT = 'root/emc' THINPROVISIONINGCOMPOSITE = 32768 THINPROVISIONING = 5 +SYNC_CLONE_LOCAL = 10 +COPY_ON_WRITE = 6 +TF_CLONE = 8 class EMCVMAXProvision(object): @@ -693,52 +696,18 @@ class EMCVMAXProvision(object): """ 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 != 0: - rc, errordesc = self.utils.wait_for_job_complete(conn, rsd, - extraSpecs) - if rc != 0: - 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() + rsdInstance = self.utils.set_copy_methodology_in_rsd( + conn, repServiceInstanceName, SYNC_CLONE_LOCAL, + COPY_ON_WRITE, extraSpecs) # 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(TF_CLONE, '16'), ReplicationSettingData=rsdInstance, SourceElement=sourceInstance.path) else: @@ -747,13 +716,13 @@ class EMCVMAXProvision(object): rc, job = conn.InvokeMethod( 'CreateElementReplica', repServiceInstanceName, ElementName=cloneName, - SyncType=self.utils.get_num(8, '16'), + SyncType=self.utils.get_num(TF_CLONE, '16'), SourceElement=sourceInstance.path) else: rc, job = conn.InvokeMethod( 'CreateElementReplica', repServiceInstanceName, ElementName=cloneName, - SyncType=self.utils.get_num(8, '16'), + SyncType=self.utils.get_num(TF_CLONE, '16'), SourceElement=sourceInstance.path, TargetElement=targetInstance.path) diff --git a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py index 3c301930a..1cf058c5e 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py @@ -31,6 +31,9 @@ EMC_ROOT = 'root/emc' THINPROVISIONINGCOMPOSITE = 32768 THINPROVISIONING = 5 INFO_SRC_V3 = 3 +ACTIVATESNAPVX = 4 +DEACTIVATESNAPVX = 19 +SNAPSYNCTYPE = 7 class EMCVMAXProvisionV3(object): @@ -169,7 +172,39 @@ class EMCVMAXProvisionV3(object): associators = conn.Associators( jobInstance, ResultClass='EMC_StorageVolume') - volpath = associators[0].path + if len(associators) > 0: + return self.create_volume_dict(associators[0].path) + else: + exceptionMessage = (_( + "Unable to get storage volume from job.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + def get_volume_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: dict -- volumeDict - an instance of a volume + """ + associators = conn.Associators( + jobInstance, + ResultClass='EMC_StorageVolume') + if len(associators) > 0: + return associators[0] + else: + exceptionMessage = (_( + "Unable to get storage volume from job.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + def create_volume_dict(self, volumeInstanceName): + """Create volume dictionary + + :param volumeInstanceName: the instance of a job + :returns: dict -- volumeDict - an instance of a volume + """ + volpath = volumeInstanceName volumeDict = {} volumeDict['classname'] = volpath.classname keys = {} @@ -184,7 +219,7 @@ class EMCVMAXProvisionV3(object): def create_element_replica( self, conn, repServiceInstanceName, cloneName, syncType, sourceInstance, extraSpecs, - targetInstance=None): + targetInstance=None, rsdInstance=None): """Make SMI-S call to create replica for source element. :param conn: the connection to the ecom server @@ -193,36 +228,36 @@ class EMCVMAXProvisionV3(object): :param syncType: 7=snapshot, 8=clone :param sourceInstance: source volume instance :param extraSpecs: additional info - :param targetInstance: target volume instance. Defaults to None + :param targetInstance: Target volume instance. Default None + :param rsdInstance: replication settingdata instance. Default None :returns: int -- rc - return code :returns: job - job object of the replica creation operation :raises: VolumeBackendAPIException """ 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}) + LOG.debug("Create replica: %(clone)s " + "syncType: %(syncType)s Source: %(source)s.", + {'clone': cloneName, + 'syncType': syncType, + 'source': sourceInstance.path}) + storageSystemName = sourceInstance['SystemName'] + __, __, sgInstanceName = ( + self.utils.get_v3_default_sg_instance_name( + conn, extraSpecs[self.utils.POOL], + extraSpecs[self.utils.SLO], + extraSpecs[self.utils.WORKLOAD], storageSystemName)) + if targetInstance is None and rsdInstance is None: rc, job = conn.InvokeMethod( 'CreateElementReplica', repServiceInstanceName, - ElementName=cloneName, SyncType=syncType, + ElementName=cloneName, + SyncType=self.utils.get_num(syncType, '16'), SourceElement=sourceInstance.path, - TargetElement=targetInstance.path) + Collections=[sgInstanceName]) + else: + rc, job = self._create_element_replica_extra_params( + conn, repServiceInstanceName, cloneName, syncType, + sourceInstance, targetInstance, rsdInstance, + sgInstanceName) if rc != 0: rc, errordesc = self.utils.wait_for_job_complete(conn, job, @@ -244,6 +279,49 @@ class EMCVMAXProvisionV3(object): time.time())}) return rc, job + def _create_element_replica_extra_params( + self, conn, repServiceInstanceName, cloneName, syncType, + sourceInstance, targetInstance, rsdInstance, sgInstanceName): + """CreateElementReplica using extra parameters. + + :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. Default None + :param rsdInstance: replication settingdata instance. Default None + :param sgInstanceName: pool instance name + :returns: int -- rc - return code + :returns: job - job object of the replica creation operation + """ + syncType = self.utils.get_num(syncType, '16') + if targetInstance and rsdInstance: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=syncType, + SourceElement=sourceInstance.path, + TargetElement=targetInstance.path, + ReplicationSettingData=rsdInstance) + elif targetInstance: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=syncType, + SourceElement=sourceInstance.path, + TargetElement=targetInstance.path) + elif rsdInstance: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=syncType, + SourceElement=sourceInstance.path, + ReplicationSettingData=rsdInstance, + Collections=[sgInstanceName]) + + return rc, job + def break_replication_relationship( self, conn, repServiceInstanceName, syncInstanceName, operation, extraSpecs, force=False): @@ -449,7 +527,7 @@ class EMCVMAXProvisionV3(object): :returns: job object of the replica creation operation """ # Operation 4: activate the snapVx. - operation = self.utils.get_num(4, '16') + operation = ACTIVATESNAPVX LOG.debug("Activate snap: %(sv)s operation: %(operation)s.", {'sv': syncInstanceName, 'operation': operation}) @@ -470,7 +548,7 @@ class EMCVMAXProvisionV3(object): :returns: job object of the replica creation operation """ # Operation 4: activate the snapVx. - operation = self.utils.get_num(19, '16') + operation = DEACTIVATESNAPVX LOG.debug("Return snap resource back to pool: " "%(sv)s operation: %(operation)s.", @@ -503,7 +581,7 @@ class EMCVMAXProvisionV3(object): rc, job = conn.InvokeMethod( 'ModifyReplicaSynchronization', repServiceInstanceName, - Operation=operation, + Operation=self.utils.get_num(operation, '16'), Synchronization=syncInstanceName, Force=force) @@ -558,15 +636,13 @@ class EMCVMAXProvisionV3(object): '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')) + SyncType=self.utils.get_num(SNAPSYNCTYPE, '16')) if rc != 0: rc, errordesc = self.utils.wait_for_job_complete(conn, job, diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 986609e50..2b185cbe8 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -410,18 +410,9 @@ class EMCVMAXUtils(object): :raises: VolumeBackendAPIException """ retries = kwargs['retries'] - maxJobRetries = self._get_max_job_retries(extraSpecs) - wait_for_sync_called = kwargs['wait_for_sync_called'] - if self._is_sync_complete(conn, syncName): - raise loopingcall.LoopingCallDone() - if retries > maxJobRetries: - LOG.error(_LE("_wait_for_sync failed after %(retries)d " - "tries."), - {'retries': retries}) - raise loopingcall.LoopingCallDone() try: kwargs['retries'] = retries + 1 - if not wait_for_sync_called: + if not kwargs['wait_for_sync_called']: if self._is_sync_complete(conn, syncName): kwargs['wait_for_sync_called'] = True except Exception: @@ -430,11 +421,21 @@ class EMCVMAXUtils(object): LOG.exception(exceptionMessage) raise exception.VolumeBackendAPIException(exceptionMessage) + if kwargs['retries'] > maxJobRetries: + LOG.error(_LE("_wait_for_sync failed after %(retries)d " + "tries."), + {'retries': retries}) + raise loopingcall.LoopingCallDone(retvalue=maxJobRetries) + if kwargs['wait_for_sync_called']: + raise loopingcall.LoopingCallDone() + + maxJobRetries = self._get_max_job_retries(extraSpecs) kwargs = {'retries': 0, 'wait_for_sync_called': False} intervalInSecs = self._get_interval_in_secs(extraSpecs) timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync) - timer.start(interval=intervalInSecs).wait() + rc = timer.start(interval=intervalInSecs).wait() + return rc def _is_sync_complete(self, conn, syncName): """Check if the job is finished. @@ -2471,3 +2472,97 @@ class EMCVMAXUtils(object): LOG.error(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) return protocolControllerInstanceName + + def get_replication_setting_data(self, conn, repServiceInstanceName, + replication_type, extraSpecs): + """Get the replication setting data + + :param conn: connection the ecom server + :param repServiceInstanceName: the storage group instance name + :param replication_type: the replication type + :param copy_methodology: the copy methodology + :returns: instance rsdInstance + """ + repServiceCapabilityInstanceNames = conn.AssociatorNames( + repServiceInstanceName, + ResultClass='CIM_ReplicationServiceCapabilities', + AssocClass='CIM_ElementCapabilities') + repServiceCapabilityInstanceName = ( + repServiceCapabilityInstanceNames[0]) + + rc, rsd = conn.InvokeMethod( + 'GetDefaultReplicationSettingData', + repServiceCapabilityInstanceName, + ReplicationType=self.get_num(replication_type, '16')) + + if rc != 0: + rc, errordesc = self.wait_for_job_complete(conn, rsd, + extraSpecs) + if rc != 0: + exceptionMessage = (_( + "Error getting ReplicationSettingData. " + "Return code: %(rc)lu. " + "Error: %(error)s.") + % {'rc': rc, + 'error': errordesc}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + return rsd + + def set_copy_methodology_in_rsd(self, conn, repServiceInstanceName, + replication_type, copy_methodology, + extraSpecs): + """Get the replication setting data + + :param conn: connection the ecom server + :param repServiceInstanceName: the storage group instance name + :param replication_type: the replication type + :param copy_methodology: the copy methodology + :returns: instance rsdInstance + """ + rsd = self.get_replication_setting_data( + conn, repServiceInstanceName, replication_type, extraSpecs) + rsdInstance = rsd['DefaultInstance'] + rsdInstance['DesiredCopyMethodology'] = ( + self.get_num(copy_methodology, '16')) + return rsdInstance + + def set_target_element_supplier_in_rsd( + self, conn, repServiceInstanceName, replication_type, + target_type, extraSpecs): + """Get the replication setting data + + :param conn: connection the ecom server + :param repServiceInstanceName: the storage group instance name + :param replication_type: the replication type + :param target_type: Use existing, Create new, Use and create + :returns: instance rsdInstance + """ + rsd = self.get_replication_setting_data( + conn, repServiceInstanceName, replication_type, extraSpecs) + rsdInstance = rsd['DefaultInstance'] + rsdInstance['TargetElementSupplier'] = ( + self.get_num(target_type, '16')) + + return rsdInstance + + def get_v3_default_sg_instance_name( + self, conn, poolName, slo, workload, storageSystemName): + """Get the V3 default instance name + + :param conn: the connection to the ecom server + :param poolName: the pool name + :param slo: the SLO + :param workload: the workload + :param storageSystemName: the storage system name + :returns: the storage group instance name + """ + storageGroupName = self.get_v3_storage_group_name( + poolName, slo, workload) + controllerConfigService = ( + self.find_controller_configuration_service( + conn, storageSystemName)) + sgInstanceName = self.find_storage_masking_group( + conn, controllerConfigService, storageGroupName) + return storageGroupName, controllerConfigService, sgInstanceName -- 2.45.2