diff --git a/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py b/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py index f25dfc141ce..df5ae844d43 100644 --- a/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py +++ b/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py @@ -3937,6 +3937,34 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.assertEqual([{'status': 'available', 'id': '2'}], volumes_model_update) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_group_sync_rg_by_target', + return_value="") + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_find_consistency_group', + return_value=(None, EMCVMAXCommonData.test_CG)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'ISCSINoFAST'}) + def test_create_consistencygroup_from_source_cg( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_rg): + volumes = [self.data.test_source_volume] + model_update, volumes_model_update = ( + self.driver.create_consistencygroup_from_src( + self.data.test_ctxt, self.data.test_CG, volumes, + source_cg=self.data.test_CG, source_vols=volumes)) + self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + model_update) + self.assertEqual([{'status': 'available', 'id': '2'}], + volumes_model_update) + @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_update_pool_stats', diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 95120d7202d..d436b56970b 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -4377,26 +4377,31 @@ class EMCVMAXCommon(object): volumes_model_update is a list of dictionaries of volume update """ - if source_cg or source_vols: - LOG.debug("The VMAX driver does not support creating a " - "consistency group from a consistency group in " - "this version.") - raise NotImplementedError() + if cgsnapshot: + sourceCgName = self.utils.truncate_string(cgsnapshot['id'], + TRUNCATE_8) + source_vols_or_snapshots = snapshots + source_id = cgsnapshot['consistencygroup_id'] + elif source_cg: + sourceCgName = self.utils.truncate_string(source_cg['id'], + TRUNCATE_8) + source_vols_or_snapshots = source_vols + source_id = source_cg['id'] + else: + exceptionMessage = (_("Must supply either CG snaphot or " + "a source CG.")) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + LOG.debug("Enter EMCVMAXCommon::create_consistencygroup_from_src. " "Group to be created: %(cgId)s, " - "Source snapshot: %(cgSnapshot)s.", + "Source : %(SourceCGId)s.", {'cgId': group['id'], - 'cgSnapshot': cgsnapshot['consistencygroup_id']}) + 'SourceCGId': source_id}) self.create_consistencygroup(context, group) targetCgName = self.utils.truncate_string(group['id'], TRUNCATE_8) - if not snapshots: - exceptionMessage = (_("No source snapshots provided to create " - "consistency group %s.") % targetCgName) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - modelUpdate = {'status': fields.ConsistencyGroupStatus.AVAILABLE} try: @@ -4413,9 +4418,14 @@ class EMCVMAXCommon(object): LOG.debug("Create CG %(targetCg)s from snapshot.", {'targetCg': targetCgInstanceName}) - for volume, snapshot in zip(volumes, snapshots): - volumeSizeInbits = int(self.utils.convert_gb_to_bits( - snapshot['volume_size'])) + for volume, source_vol_or_snapshot in zip( + volumes, source_vols_or_snapshots): + if 'size' in source_vol_or_snapshot: + volumeSizeInbits = int(self.utils.convert_gb_to_bits( + source_vol_or_snapshot['size'])) + else: + volumeSizeInbits = int(self.utils.convert_gb_to_bits( + source_vol_or_snapshot['volume_size'])) targetVolumeName = 'targetVol' volume = {'size': int(self.utils.convert_bits_to_gbs( volumeSizeInbits))} @@ -4432,9 +4442,9 @@ class EMCVMAXCommon(object): targetVolumeInstance = self.utils.find_volume_instance( self.conn, volumeDict, targetVolumeName) LOG.debug("Create target volume for member snapshot. " - "Source snapshot: %(snapshot)s, " + "Source : %(snapshot)s, " "Target volume: %(targetVol)s.", - {'snapshot': snapshot['id'], + {'snapshot': source_vol_or_snapshot['id'], 'targetVol': targetVolumeInstance.path}) self.provision.add_volume_to_cg(self.conn, @@ -4444,13 +4454,12 @@ class EMCVMAXCommon(object): targetCgName, targetVolumeName, extraSpecs) - sourceCgInstanceName = self._find_consistency_group( - replicationService, str(cgsnapshot['id'])) + replicationService, sourceCgName) if sourceCgInstanceName is None: exceptionMessage = (_("Cannot find source CG instance. " "consistencygroup_id: %s.") % - cgsnapshot['consistencygroup_id']) + source_id) raise exception.VolumeBackendAPIException( data=exceptionMessage) relationName = self.utils.truncate_string(group['id'], TRUNCATE_5) @@ -4479,11 +4488,10 @@ class EMCVMAXCommon(object): self.conn, replicationService, rgSyncInstanceName, extraSpecs) except Exception: - cgSnapshotId = cgsnapshot['consistencygroup_id'] exceptionMessage = (_("Failed to create CG %(cgName)s " - "from snapshot %(cgSnapshot)s.") + "from source %(cgSnapshot)s.") % {'cgName': targetCgName, - 'cgSnapshot': cgSnapshotId}) + 'cgSnapshot': source_id}) LOG.exception(exceptionMessage) raise exception.VolumeBackendAPIException(data=exceptionMessage) volumes_model_update = self.utils.get_volume_model_updates( diff --git a/releasenotes/notes/vmax-clone-cg-09fce492931c957f.yaml b/releasenotes/notes/vmax-clone-cg-09fce492931c957f.yaml new file mode 100644 index 00000000000..8efc0c1381b --- /dev/null +++ b/releasenotes/notes/vmax-clone-cg-09fce492931c957f.yaml @@ -0,0 +1,3 @@ +--- +features: + VMAX Driver - Create a CG from a source CG