From 9218a0b225c77d2fb1af2901a88dc8e8b0cd4c91 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Tue, 23 Jun 2015 16:49:37 -0400 Subject: [PATCH] EMC VMAX Modify CG This patch adds support for modify CG in VMAX driver. implements blueprint emc-vmax-modify-cg Change-Id: I23087f6b15dd305fa4e882ea4d8f30414a63c9c8 --- cinder/tests/unit/test_emc_vmax.py | 151 ++++++++++++++++++ cinder/volume/drivers/emc/emc_vmax_common.py | 77 ++++++++- cinder/volume/drivers/emc/emc_vmax_fc.py | 9 +- cinder/volume/drivers/emc/emc_vmax_iscsi.py | 13 +- .../volume/drivers/emc/emc_vmax_provision.py | 31 ++-- 5 files changed, 266 insertions(+), 15 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index c1a37d271..06da6349c 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -1134,6 +1134,11 @@ class FakeEcomConnection(object): replic_service['CreationClassName'] = \ self.data.replication_service_creationclass replic_services.append(replic_service) + replic_service2 = {} + replic_service2['SystemName'] = self.data.storage_system_v3 + replic_service2['CreationClassName'] = ( + self.data.replication_service_creationclass) + replic_services.append(replic_service2) return replic_services def _enum_pools(self): @@ -2979,6 +2984,57 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.delete_cgsnapshot( self.data.test_ctxt, self.data.test_CG_snapshot) + @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_update_CG_add_volume_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + add_volumes = [] + add_volumes.append(self.data.test_source_volume) + remove_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + add_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Can't find CG + self.driver.common._find_consistency_group = mock.Mock( + return_value=None) + self.assertRaises(exception.ConsistencyGroupNotFound, + self.driver.update_consistencygroup, + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + + @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_update_CG_remove_volume_no_fast_success( + self, _mock_volume_type, _mock_storage_system): + remove_volumes = [] + remove_volumes.append(self.data.test_source_volume) + add_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + remove_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Bug https://bugs.launchpad.net/cinder/+bug/1442376 @mock.patch.object( emc_vmax_common.EMCVMAXCommon, @@ -3769,6 +3825,50 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): self.driver.delete_cgsnapshot( self.data.test_ctxt, self.data.test_CG_snapshot) + @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': 'ISCSIFAST'}) + def test_update_CG_add_volume_fast_success( + self, _mock_volume_type, _mock_storage_system): + add_volumes = [] + add_volumes.append(self.data.test_source_volume) + remove_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + add_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + + @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': 'ISCSIFAST'}) + def test_update_CG_remove_volume_fast_success( + self, _mock_volume_type, _mock_storage_system): + remove_volumes = [] + remove_volumes.append(self.data.test_source_volume) + add_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + remove_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -5456,6 +5556,57 @@ class EMCV3DriverTestCase(test.TestCase): self.driver.delete_cgsnapshot( self.data.test_ctxt, self.data.test_CG_snapshot) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system_v3)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + def test_update_CG_add_volume_v3_success( + self, _mock_volume_type, _mock_storage_system): + add_volumes = [] + add_volumes.append(self.data.test_source_volume) + remove_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + add_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Can't find CG + self.driver.common._find_consistency_group = mock.Mock( + return_value=None) + self.assertRaises(exception.ConsistencyGroupNotFound, + self.driver.update_consistencygroup, + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system_v3)) + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'volume_backend_name': 'V3_BE'}) + def test_update_CG_remove_volume_v3_success( + self, _mock_volume_type, _mock_storage_system): + remove_volumes = [] + remove_volumes.append(self.data.test_source_volume) + add_volumes = None + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + # Multiple volumes + remove_volumes.append(self.data.test_source_volume) + self.driver.update_consistencygroup( + self.data.test_ctxt, self.data.test_CG, + add_volumes, remove_volumes) + @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_is_same_host', diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 36d071f26..3ca9924c4 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -19,7 +19,6 @@ import os.path from oslo_config import cfg from oslo_log import log as logging from oslo_utils import units - import six from cinder import exception @@ -4068,3 +4067,79 @@ class EMCVMAXCommon(object): volumeInstance = self.utils.rename_volume(self.conn, volumeInstance, volumeId) + + def update_consistencygroup(self, group, add_volumes, + remove_volumes): + """Updates LUNs in consistency group. + + :param group: storage configuration service instance + :param add_volumes: the volumes uuids you want to add to the CG + :param remove_volumes: the volumes uuids you want to remove from + the CG + """ + LOG.info(_LI("Update Consistency Group: %(group)s. " + "This adds and/or removes volumes from a CG."), + {'group': group['id']}) + + modelUpdate = {'status': 'available'} + volumeTypeId = group['volume_type_id'].replace(",", "") + + cg_name = self.utils.truncate_string(group['id'], 8) + + extraSpecs = self._initial_setup(None, volumeTypeId) + + _poolInstanceName, storageSystem = ( + self._get_pool_and_storage_system(extraSpecs)) + add_vols = [vol for vol in add_volumes] if add_volumes else [] + add_instance_names = self._get_volume_instance_names(add_vols) + remove_vols = [vol for vol in remove_volumes] if remove_volumes else [] + remove_instance_names = self._get_volume_instance_names(remove_vols) + self.conn = self._get_ecom_connection() + + try: + replicationService = self.utils.find_replication_service( + self.conn, storageSystem) + cgInstanceName = ( + self._find_consistency_group(replicationService, cg_name)) + if cgInstanceName is None: + raise exception.ConsistencyGroupNotFound( + consistencygroup_id=cg_name) + # Add volume(s) to a consistency group + if add_instance_names: + self.provision.add_volume_to_cg( + self.conn, replicationService, cgInstanceName, + add_instance_names, cg_name, None, + extraSpecs) + # Remove volume(s) from a consistency group + if remove_instance_names: + self.provision.remove_volume_from_cg( + self.conn, replicationService, cgInstanceName, + remove_instance_names, cg_name, None, + extraSpecs) + except exception.ConsistencyGroupNotFound: + raise + except Exception as ex: + LOG.error(_LE("Exception: %(ex)s"), {'ex': ex}) + exceptionMessage = (_("Failed to update consistency group:" + " %(cgName)s.") + % {'cgName': cg_name}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + return modelUpdate, None, None + + def _get_volume_instance_names(self, volumes): + """Get volume instance names from volume. + + :param volumes: volume objects + :returns: volume instance names + """ + volumeInstanceNames = [] + for volume in volumes: + volumeInstance = self._find_lun(volume) + if volumeInstance is None: + LOG.error(_LE("Volume %(name)s not found on the array."), + {'name': volume['name']}) + else: + volumeInstanceNames.append(volumeInstance.path) + return volumeInstanceNames diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 8f031a2d0..93da018b7 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -39,9 +39,10 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 2.1.3 - Fixed a problem with FAST support (bug #1435069) 2.2.0 - Add manage/unmanage 2.2.1 - Support for SE 8.0.3 + 2.2.2 - Update Consistency Group """ - VERSION = "2.2.1" + VERSION = "2.2.2" def __init__(self, *args, **kwargs): @@ -347,3 +348,9 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): Leave the volume intact on the backend array. """ return self.common.unmanage(volume) + + def update_consistencygroup(self, context, group, + add_volumes, remove_volumes): + """Updates LUNs in consistency group.""" + return self.common.update_consistencygroup(group, add_volumes, + remove_volumes) diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 35c6a2d47..9d60ee457 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -47,16 +47,17 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 2.1.3 - Fixed a problem with FAST support (bug #1435069) 2.2.0 - Add manage/unmanage 2.2.1 - Support for SE 8.0.3 + 2.2.2 - Update Consistency Group """ - VERSION = "2.2.1" + VERSION = "2.2.2" def __init__(self, *args, **kwargs): super(EMCVMAXISCSIDriver, self).__init__(*args, **kwargs) - self.common =\ + self.common = ( emc_vmax_common.EMCVMAXCommon('iSCSI', - configuration=self.configuration) + configuration=self.configuration)) def check_for_setup_error(self): pass @@ -352,3 +353,9 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): backend array. """ return self.common.unmanage(volume) + + def update_consistencygroup(self, context, group, + add_volumes, remove_volumes): + """Updates LUNs in consistency group.""" + return self.common.update_consistencygroup(group, add_volumes, + remove_volumes) diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 9a234490a..4948a8cb8 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision.py @@ -967,10 +967,16 @@ class EMCVMAXProvision(object): """ startTime = time.time() + if isinstance(volumeInstanceName, list): + theElements = volumeInstanceName + volumeName = 'Bulk Add' + else: + theElements = [volumeInstanceName] + rc, job = conn.InvokeMethod( 'AddMembers', replicationService, - Members=[volumeInstanceName], + Members=theElements, ReplicationGroup=cgInstanceName) if rc != 0: @@ -978,9 +984,9 @@ class EMCVMAXProvision(object): extraSpecs) if rc != 0: exceptionMessage = (_( - "Failed to add volume %(volumeName)s: " - "to consistency group %(cgName)s " - "Return code: %(rc)lu. Error: %(error)s.") + "Failed to add volume %(volumeName)s " + "to consistency group %(cgName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'volumeName': volumeName, 'cgName': cgName, 'rc': rc, @@ -1013,21 +1019,26 @@ class EMCVMAXProvision(object): """ startTime = time.time() + if isinstance(volumeInstanceName, list): + theElements = volumeInstanceName + volumeName = 'Bulk Remove' + else: + theElements = [volumeInstanceName] + rc, job = conn.InvokeMethod( 'RemoveMembers', replicationService, - Members=[volumeInstanceName], - ReplicationGroup=cgInstanceName, - RemoveElements=True) + Members=theElements, + ReplicationGroup=cgInstanceName) if rc != 0: rc, errordesc = self.utils.wait_for_job_complete(conn, job, extraSpecs) if rc != 0: exceptionMessage = (_( - "Failed to remove volume %(volumeName)s: " - "to consistency group %(cgName)s " - "Return code: %(rc)lu. Error: %(error)s.") + "Failed to remove volume %(volumeName)s " + "from consistency group %(cgName)s. " + "Return code: %(rc)lu. Error: %(error)s.") % {'volumeName': volumeName, 'cgName': cgName, 'rc': rc, -- 2.45.2