From 93974f845da9279d2e1f713c8c39200f470f2d96 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Thu, 13 Aug 2015 09:15:33 -0400 Subject: [PATCH] EMC VMAX Create CG from CG Snapshot This patch adds support for create CG from CG snapshot in the VMAX driver. implements blueprint emc-vmax-create-cg-from-cgsnapshot Change-Id: Iab13167382ceebbc6eaa165c431c5a09796ce3e9 --- cinder/tests/unit/test_emc_vmax.py | 39 +++++ cinder/volume/drivers/emc/emc_vmax_common.py | 146 ++++++++++++++++++- cinder/volume/drivers/emc/emc_vmax_fc.py | 21 ++- cinder/volume/drivers/emc/emc_vmax_iscsi.py | 21 ++- cinder/volume/drivers/emc/emc_vmax_utils.py | 24 +++ 5 files changed, 246 insertions(+), 5 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index 7d7109bf8..7c34d3014 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -3151,6 +3151,45 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): conn, volumeInstance, originalName) self.assertEqual(originalName, volumeInstance['ElementName']) + def test_get_volume_model_updates(self): + utils = self.driver.common.utils + status = 'status-string' + volumes = utils.get_volume_model_updates( + None, self.driver.db, self.data.test_CG['id'], + status) + self.assertEqual(status, volumes[0]['status']) + + @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_src( + self, _mock_volume_type, _mock_storage, _mock_cg, _mock_rg): + volumes = [] + volumes.append(self.data.test_source_volume) + snapshots = [] + self.data.test_snapshot['volume_size'] = "10" + snapshots.append(self.data.test_snapshot) + model_update, volumes_model_update = ( + self.driver.create_consistencygroup_from_src( + self.data.test_ctxt, self.data.test_CG, volumes, + self.data.test_CG_snapshot, snapshots)) + self.assertEqual({'status': 'available'}, model_update) + self.assertEqual([{'status': 'available', 'id': '2'}], + volumes_model_update) + def _cleanup(self): if self.config_file_path: 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 402d623fa..ae9701d06 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -64,6 +64,8 @@ WORKLOAD = 'storagetype:workload' INTERVAL = 'storagetype:interval' RETRIES = 'storagetype:retries' ISV3 = 'isV3' +TRUNCATE_5 = 5 +TRUNCATE_8 = 8 emc_opts = [ cfg.StrOpt('cinder_emc_config_file', @@ -2523,8 +2525,8 @@ class EMCVMAXCommon(object): 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.", + "Source volume: %(memberVol)s " + "Target volume %(targetVol)s.", {'memberVol': memberInstanceName, 'targetVol': targetVolumeInstance.path}) self.provision.add_volume_to_cg(self.conn, @@ -3742,7 +3744,7 @@ class EMCVMAXCommon(object): # 8 - Detach operation. # 9 - Dissolve operation. if isSnapshot: - # Operation 7: dissolve for snapVx. + # Operation 9: dissolve for snapVx. operation = self.utils.get_num(9, '16') else: # Operation 8: detach for clone. @@ -4132,3 +4134,141 @@ class EMCVMAXCommon(object): else: volumeInstanceNames.append(volumeInstance.path) return volumeInstanceNames + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot, snapshots, db): + """Creates the consistency group from source. + + Currently the source can only be a cgsnapshot. + + :param context: the context + :param group: the consistency group object to be created + :param volumes: volumes in the consistency group + :param cgsnapshot: the source consistency group snapshot + :param snapshots: snapshots of the source volumes + :param db: database + :returns: model_update, volumes_model_update + model_update is a dictionary of cg status + volumes_model_update is a list of dictionaries of volume + update + """ + LOG.debug("Enter EMCVMAXCommon::create_consistencygroup_from_src. " + "Group to be created: %(cgId)s, " + "Source snapshot: %(cgSnapshot)s.", + {'cgId': group['id'], + 'cgSnapshot': cgsnapshot['consistencygroup_id']}) + + volumeTypeId = group['volume_type_id'].replace(",", "") + extraSpecs = self._initial_setup(None, volumeTypeId) + + 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': 'available'} + + _poolInstanceName, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + try: + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + if replicationService is None: + exceptionMessage = (_( + "Cannot find replication service on system %s.") % + storageSystem) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + targetCgInstanceName = self._find_consistency_group( + replicationService, targetCgName) + 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'])) + targetVolumeName = 'targetVol' + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + if extraSpecs[ISV3]: + _rc, volumeDict, _storageSystemName = ( + self._create_v3_volume( + volume, targetVolumeName, volumeSizeInbits, + extraSpecs)) + else: + _rc, volumeDict, _storageSystemName = ( + self._create_composite_volume( + volume, targetVolumeName, volumeSizeInbits, + extraSpecs)) + targetVolumeInstance = self.utils.find_volume_instance( + self.conn, volumeDict, targetVolumeName) + LOG.debug("Create target volume for member snapshot. " + "Source snapshot: %(snapshot)s, " + "Target volume: %(targetVol)s.", + {'snapshot': snapshot['id'], + 'targetVol': targetVolumeInstance.path}) + + self.provision.add_volume_to_cg(self.conn, + replicationService, + targetCgInstanceName, + targetVolumeInstance.path, + targetCgName, + targetVolumeName, + extraSpecs) + + sourceCgName = self.utils.truncate_string(cgsnapshot['id'], + TRUNCATE_8) + sourceCgInstanceName = self._find_consistency_group( + replicationService, sourceCgName) + if sourceCgInstanceName is None: + exceptionMessage = (_("Cannot find source CG instance. " + "consistencygroup_id: %s.") % + cgsnapshot['consistencygroup_id']) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + relationName = self.utils.truncate_string(group['id'], TRUNCATE_5) + if extraSpecs[ISV3]: + self.provisionv3.create_group_replica( + self.conn, replicationService, sourceCgInstanceName, + targetCgInstanceName, relationName, extraSpecs) + else: + self.provision.create_group_replica( + self.conn, replicationService, sourceCgInstanceName, + targetCgInstanceName, relationName, extraSpecs) + # Break the replica group relationship. + rgSyncInstanceName = self.utils.find_group_sync_rg_by_target( + self.conn, storageSystem, targetCgInstanceName, extraSpecs, + True) + + if rgSyncInstanceName is not None: + if extraSpecs[ISV3]: + # Operation 9: dissolve for snapVx + operation = self.utils.get_num(9, '16') + self.provisionv3.break_replication_relationship( + self.conn, replicationService, rgSyncInstanceName, + operation, extraSpecs) + else: + self.provision.delete_clone_relationship( + self.conn, replicationService, + rgSyncInstanceName, extraSpecs) + except Exception as ex: + modelUpdate['status'] = 'error' + cgSnapshotId = cgsnapshot['consistencygroup_id'] + volumes_model_update = self.utils.get_volume_model_updates( + context, db, group['id'], modelUpdate['status']) + LOG.error(_LE("Exception: %(ex)s."), {'ex': ex}) + exceptionMessage = (_("Failed to create CG %(cgName)s " + "from snapshot %(cgSnapshot)s.") + % {'cgName': targetCgName, + 'cgSnapshot': cgSnapshotId}) + LOG.error(exceptionMessage) + return modelUpdate, volumes_model_update + + volumes_model_update = self.utils.get_volume_model_updates( + context, db, group['id'], modelUpdate['status']) + + return modelUpdate, volumes_model_update diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 08376cd3f..13404bc42 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -41,9 +41,10 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 2.2.1 - Support for SE 8.0.3 2.2.2 - Update Consistency Group 2.2.3 - Pool aware scheduler(multi-pool) support + 2.2.4 - Create CG from CG snapshot """ - VERSION = "2.2.3" + VERSION = "2.2.4" def __init__(self, *args, **kwargs): @@ -356,3 +357,21 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): """Updates LUNs in consistency group.""" return self.common.update_consistencygroup(group, add_volumes, remove_volumes) + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None, + source_cg=None, source_vols=None): + """Creates the consistency group from source. + + Currently the source can only be a cgsnapshot. + + :param context: the context + :param group: the consistency group object to be created + :param volumes: volumes in the consistency group + :param cgsnapshot: the source consistency group snapshot + :param snapshots: snapshots of the source volumes + :param source_cg: the dictionary of a consistency group as source. + :param source_vols: a list of volume dictionaries in the source_cg. + """ + return self.common.create_consistencygroup_from_src( + context, group, volumes, cgsnapshot, snapshots, self.db) diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index cfae7121f..179cada10 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -49,9 +49,10 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 2.2.1 - Support for SE 8.0.3 2.2.2 - Update Consistency Group 2.2.3 - Pool aware scheduler(multi-pool) support + 2.2.4 - Create CG from CG snapshot """ - VERSION = "2.2.3" + VERSION = "2.2.4" def __init__(self, *args, **kwargs): @@ -359,3 +360,21 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): """Updates LUNs in consistency group.""" return self.common.update_consistencygroup(group, add_volumes, remove_volumes) + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None, + source_cg=None, source_vols=None): + """Creates the consistency group from source. + + Currently the source can only be a cgsnapshot. + + :param context: the context + :param group: the consistency group object to be created + :param volumes: volumes in the consistency group + :param cgsnapshot: the source consistency group snapshot + :param snapshots: snapshots of the source volumes + :param source_cg: the dictionary of a consistency group as source. + :param source_vols: a list of volume dictionaries in the source_cg. + """ + return self.common.create_consistencygroup_from_src( + context, group, volumes, cgsnapshot, snapshots, self.db) diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 41457ca35..722c33ef8 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -2259,3 +2259,27 @@ class EMCVMAXUtils(object): {'source': sourceDeviceId, 'storageSystem': storageSystem}) return foundSyncInstanceName + + def get_volume_model_updates( + self, context, db, cgId, status='available'): + """Update the volume model's status and return it. + + :param context: the context + :param db: cinder database + :param cgId: cg id + :param status: string value reflects the status of the member volume + :returns: volume_model_updates - updated volumes + """ + volume_model_updates = [] + volumes = db.volume_get_all_by_group(context, cgId) + LOG.info(_LI( + "Updating status for CG: %(id)s."), + {'id': cgId}) + if volumes: + for volume in volumes: + volume_model_updates.append({'id': volume['id'], + 'status': status}) + else: + LOG.info(_LI("No volume found for CG: %(cg)s."), + {'cg': cgId}) + return volume_model_updates -- 2.45.2