From 49bda16e5a57497cb6dfe0f85748a7e8dde3be9c Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Thu, 26 Feb 2015 12:27:16 -0500 Subject: [PATCH] Check license before clone in VMAX driver This patch checks if a license for clone is available before doing the clone operation. Change-Id: I69d009b2cac775c301e9a5254079238b2b2a3b10 Closes-Bug: #1385450 --- cinder/tests/test_emc_vmax.py | 94 +++++++++++--------- cinder/volume/drivers/emc/emc_vmax_common.py | 12 +++ cinder/volume/drivers/emc/emc_vmax_utils.py | 50 +++++++++++ 3 files changed, 112 insertions(+), 44 deletions(-) diff --git a/cinder/tests/test_emc_vmax.py b/cinder/tests/test_emc_vmax.py index d54119f2d..996929a0c 100644 --- a/cinder/tests/test_emc_vmax.py +++ b/cinder/tests/test_emc_vmax.py @@ -138,6 +138,11 @@ class Fake_CIMProperty(): cimproperty.value = 'OS-myhost-MV' return cimproperty + def fake_getSupportedReplicationTypes(self): + cimproperty = Fake_CIMProperty() + cimproperty.value = [2L, 10L] + return cimproperty + class Fake_CIM_TierPolicyServiceCapabilities(): @@ -540,6 +545,8 @@ class FakeEcomConnection(): result = self._enum_storagesystems() elif name == 'Symm_TierPolicyRule': result = self._enum_policyrules() + elif name == 'CIM_ReplicationServiceCapabilities': + result = self._enum_repservcpbls() else: result = self._default_enum() return result @@ -590,6 +597,8 @@ class FakeEcomConnection(): result = self._getinstance_storagehardwareid(objectpath) elif name == 'Symm_VirtualProvisioningPool': result = self._getinstance_pool(objectpath) + elif name == 'Symm_ReplicationServiceCapabilities': + result = self._getinstance_replicationServCapabilities(objectpath) else: result = self._default_getinstance(objectpath) @@ -672,6 +681,8 @@ class FakeEcomConnection(): result = self._enum_initMaskingGroup() elif ResultClass == 'Symm_LunMaskingView': result = self._enum_maskingView() + elif ResultClass == 'EMC_Meta': + result = self._enum_metavolume() else: result = self._default_assocnames(objectpath) return result @@ -1013,6 +1024,15 @@ class FakeEcomConnection(): svInstance['PercentSynced'] = 100 return svInstance + def _getinstance_replicationServCapabilities(self, objectpath): + repServCpblInstance = SYMM_SrpStoragePool() + classcimproperty = Fake_CIMProperty() + repTypesCimproperty = ( + classcimproperty.fake_getSupportedReplicationTypes()) + properties = {u'SupportedReplicationTypes': repTypesCimproperty} + repServCpblInstance.properties = properties + return repServCpblInstance + def _default_getinstance(self, objectpath): return objectpath @@ -1377,6 +1397,9 @@ class FakeEcomConnection(): portgroups.append(portgroup) return portgroups + def _enum_metavolume(self): + return [] + def _default_enum(self): names = [] name = {} @@ -2995,31 +3018,6 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): self.data.test_volume, EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'get_volume_meta_head', - return_value=None) - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - @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': 'ISCSIFAST'}) - 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) - @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -4098,28 +4096,23 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.data.test_volume, EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'get_volume_meta_head', - return_value=None) - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - @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': 'FCFAST'}) - def test_create_clone_simple_volume_fast_success( - self, mock_volume_type, mock_volume, mock_sync_sv, - mock_simple_volume): + def test_create_clone_simple_volume_fast_success(self): + extraSpecs = {'storagetype:fastpolicy': 'FC_GOLD1', + 'volume_backend_name': 'FCFAST', + 'isV3': False} + self.driver.common._initial_setup = ( + mock.Mock(return_value=extraSpecs)) + self.driver.common.extraSpecs = extraSpecs + self.driver.utils.is_clone_licensed = ( + mock.Mock(return_value=True)) + FakeDB.volume_get = ( + mock.Mock(return_value=EMCVMAXCommonData.test_source_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.utils.isArrayV3 = mock.Mock(return_value=False) + self.driver.common._find_storage_sync_sv_sv = ( + mock.Mock(return_value=(None, None))) self.driver.create_cloned_volume(self.data.test_volume, EMCVMAXCommonData.test_source_volume) @@ -4271,6 +4264,19 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.driver.delete_cgsnapshot( self.data.test_ctxt, self.data.test_CG_snapshot) + # Bug 1385450 + def test_create_clone_without_license(self): + mockRepServCap = {} + mockRepServCap['InstanceID'] = 'SYMMETRIX+1385450' + self.driver.utils.find_replication_service_capabilities = ( + mock.Mock(return_value=mockRepServCap)) + self.driver.utils.is_clone_licensed = ( + mock.Mock(return_value=False)) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cloned_volume, + self.data.test_volume, + EMCVMAXCommonData.test_source_volume) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 17552329f..1c3945c25 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -1947,6 +1947,18 @@ class EMCVMAXCommon(object): sourceInstance = self._find_lun(sourceVolume) storageSystem = sourceInstance['SystemName'] + repServCapabilityInstanceName = ( + self.utils.find_replication_service_capabilities(self.conn, + storageSystem)) + is_clone_license = self.utils.is_clone_licensed( + self.conn, repServCapabilityInstanceName) + + if is_clone_license is False: + exceptionMessage = (_( + "Clone feature is not licensed on %(storageSystem)s.") + % {'storageSystem': storageSystem}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) repServiceInstanceName = self.utils.find_replication_service( self.conn, storageSystem) diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 44bd8a0da..4b92ee976 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -38,6 +38,7 @@ except ImportError: STORAGEGROUPTYPE = 4 POSTGROUPTYPE = 3 +CLONE_REPLICATION_TYPE = 10 EMC_ROOT = 'root/emc' CONCATENATED = 'concatenated' @@ -1838,3 +1839,52 @@ class EMCVMAXUtils(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) return instance + + def find_replication_service_capabilities(self, conn, storageSystemName): + """Find the replication service capabilities instance name. + + :param conn: the connection to the ecom server + :param storageSystemName: the storage system name + :returns: foundRepServCapability + """ + foundRepServCapability = None + repservices = conn.EnumerateInstanceNames( + 'CIM_ReplicationServiceCapabilities') + for repservCap in repservices: + if storageSystemName in repservCap['InstanceID']: + foundRepServCapability = repservCap + LOG.debug("Found Replication Service Capabilities: " + "%(repservCap)s", + {'repservCap': repservCap}) + break + if foundRepServCapability is None: + exceptionMessage = (_("Replication Service Capability not found " + "on %(storageSystemName)s.") + % {'storageSystemName': storageSystemName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + return foundRepServCapability + + def is_clone_licensed(self, conn, capabilityInstanceName): + """Check if the clone feature is licensed and enabled. + + :param conn: the connection to the ecom server + :param capabilityInstanceName: the replication service capabilities + instance name + :returns: True if licensed and enabled; False otherwise. + """ + capabilityInstance = conn.GetInstance(capabilityInstanceName) + propertiesList = capabilityInstance.properties.items() + for properties in propertiesList: + if properties[0] == 'SupportedReplicationTypes': + cimProperties = properties[1] + repTypes = cimProperties.value + LOG.debug("Found supported replication types: " + "%(repTypes)s", + {'repTypes': repTypes}) + if CLONE_REPLICATION_TYPE in repTypes: + # Clone is a supported replication type. + LOG.debug("Clone is licensed and enabled.") + return True + return False -- 2.45.2