From 3bedca6101556661359982e5c7faf8f6b3b46edd Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Tue, 12 May 2015 17:40:27 -0400 Subject: [PATCH] EMC VMAX Manage/Unmanage Volume This patch adds support for manage/unmanage volume in the VMAX driver. implements blueprint emc-vmax-manage-unmanage Change-Id: If944108526e75c34dccb1d4a071131feb4cd33e4 --- cinder/tests/unit/test_emc_vmax.py | 248 ++++++++++++++++++- cinder/volume/drivers/emc/emc_vmax_common.py | 164 ++++++++++++ cinder/volume/drivers/emc/emc_vmax_fc.py | 28 ++- cinder/volume/drivers/emc/emc_vmax_iscsi.py | 27 +- cinder/volume/drivers/emc/emc_vmax_utils.py | 132 ++++++++++ 5 files changed, 595 insertions(+), 4 deletions(-) diff --git a/cinder/tests/unit/test_emc_vmax.py b/cinder/tests/unit/test_emc_vmax.py index f141a010c..9ca427cb0 100644 --- a/cinder/tests/unit/test_emc_vmax.py +++ b/cinder/tests/unit/test_emc_vmax.py @@ -21,6 +21,7 @@ from xml.dom import minidom import mock from oslo_log import log as logging +from oslo_utils import units import six from cinder import exception @@ -273,7 +274,7 @@ class EMCVMAXCommonData(object): subscribedcapacity_bits = '500000000000' totalmanagedspace_gbs = 931 subscribedcapacity_gbs = 466 - + fake_host = 'HostX@Backend#gold+1234567891011' unit_creationclass = 'CIM_ProtocolControllerForUnit' storage_type = 'gold' keybindings = {'CreationClassName': u'Symm_StorageVolume', @@ -540,6 +541,8 @@ class FakeEcomConnection(object): result = self._enum_storagevolumes() elif name == 'Symm_StorageVolume': result = self._enum_storagevolumes() + elif name == 'CIM_StorageVolume': + result = self._enum_storagevolumes() elif name == 'CIM_ProtocolControllerForUnit': result = self._enum_unitnames() elif name == 'EMC_LunMaskingSCSIProtocolController': @@ -556,6 +559,8 @@ class FakeEcomConnection(object): result = self._enum_policyrules() elif name == 'CIM_ReplicationServiceCapabilities': result = self._enum_repservcpbls() + elif name == 'SE_StorageSynchronized_SV_SV': + result = self._enum_storageSyncSvSv() else: result = self._default_enum() return result @@ -613,6 +618,9 @@ class FakeEcomConnection(object): return result + def ModifyInstance(self, objectpath, PropertyList=None): + pass + def DeleteInstance(self, objectpath): pass @@ -1440,6 +1448,21 @@ class FakeEcomConnection(object): def _enum_metavolume(self): return [] + def _enum_storageSyncSvSv(self): + conn = FakeEcomConnection() + sourceVolume = {} + sourceVolume['CreationClassName'] = 'Symm_StorageVolume' + sourceVolume['DeviceID'] = self.data.test_volume['device_id'] + sourceInstanceName = conn.GetInstance(sourceVolume) + svInstances = [] + svInstance = {} + svInstance['SyncedElement'] = 'SyncedElement' + svInstance['SystemElement'] = sourceInstanceName + svInstance['CreationClassName'] = 'SE_StorageSynchronized_SV_SV' + svInstance['PercentSynced'] = 100 + svInstances.append(svInstance) + return svInstances + def _default_enum(self): names = [] name = {} @@ -2886,6 +2909,79 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): common._create_composite_volume.assert_called_with( volume, "TargetBaseVol", 1234567, extraSpecs, 1) + def test_find_volume_by_device_id_on_array(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + volumeInstanceName = utils.find_volume_by_device_id_on_array( + conn, self.data.storage_system, self.data.test_volume['device_id']) + expectVolume = {} + expectVolume['CreationClassName'] = 'Symm_StorageVolume' + expectVolume['DeviceID'] = self.data.test_volume['device_id'] + expect = conn.GetInstance(expectVolume) + self.assertEqual(volumeInstanceName, expect) + + def test_get_volume_element_name(self): + volumeId = 'ea95aa39-080b-4f11-9856-a03acf9112ad' + utils = self.driver.common.utils + volumeElementName = utils.get_volume_element_name(volumeId) + expectVolumeElementName = ( + emc_vmax_utils.VOLUME_ELEMENT_NAME_PREFIX + volumeId) + self.assertEqual(volumeElementName, expectVolumeElementName) + + def test_get_associated_replication_from_source_volume(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + repInstanceName = ( + utils.get_associated_replication_from_source_volume( + conn, self.data.storage_system, + self.data.test_volume['device_id'])) + expectInstanceName = ( + conn.EnumerateInstanceNames('SE_StorageSynchronized_SV_SV')[0]) + self.assertEqual(repInstanceName, expectInstanceName) + + def test_get_array_and_device_id_success(self): + deviceId = '0123' + arrayId = u'array1234' + external_ref = {u'source-name': deviceId} + volume = {'volume_metadata': [{'key': 'array', 'value': arrayId}] + } + utils = self.driver.common.utils + (arrId, devId) = utils.get_array_and_device_id(volume, external_ref) + self.assertEqual(arrId, arrayId) + self.assertEqual(devId, deviceId) + + def test_get_array_and_device_id_failed(self): + deviceId = '0123' + arrayId = u'array1234' + external_ref = {u'no-source-name': deviceId} + volume = {'volume_metadata': [{'key': 'array', 'value': arrayId}] + } + utils = self.driver.common.utils + self.assertRaises(exception.VolumeBackendAPIException, + utils.get_array_and_device_id, + volume, + external_ref) + + def test_rename_volume(self): + conn = self.fake_ecom_connection() + utils = self.driver.common.utils + newName = 'new_name' + volume = {} + volume['CreationClassName'] = 'Symm_StorageVolume' + volume['DeviceID'] = '1' + volume['ElementName'] = 'original_name' + pywbem = mock.Mock() + pywbem.cim_obj = mock.Mock() + pywbem.cim_obj.CIMInstance = mock.Mock() + emc_vmax_utils.pywbem = pywbem + volumeInstance = conn.GetInstance(volume) + originalName = volumeInstance['ElementName'] + volumeInstance = utils.rename_volume(conn, volumeInstance, newName) + self.assertEqual(newName, volumeInstance['ElementName']) + volumeInstance = utils.rename_volume( + conn, volumeInstance, originalName) + self.assertEqual(originalName, volumeInstance['ElementName']) + def _cleanup(self): if self.config_file_path: bExists = os.path.exists(self.config_file_path) @@ -3965,6 +4061,135 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): if bExists: os.remove(self.config_file_parse_port_group) + def test_manage_existing_get_size(self): + volume = {} + metadata = {'key': 'array', + 'value': '12345'} + volume['volume_metadata'] = [metadata] + external_ref = {'source-name': '0123'} + utils = self.driver.common.utils + gbSize = 2 + utils.get_volume_size = mock.Mock( + return_value=gbSize * units.Gi) + volumeInstanceName = {'CreationClassName': "Symm_StorageVolume", + 'DeviceID': "0123", + 'SystemName': "12345"} + utils.find_volume_by_device_id_on_array = mock.Mock( + return_value=volumeInstanceName) + size = self.driver.manage_existing_get_size(volume, external_ref) + self.assertEqual(gbSize, size) + + def test_manage_existing_no_fast_success(self): + volume = {} + metadata = {'key': 'array', + 'value': '12345'} + poolInstanceName = {} + storageSystem = {} + poolInstanceName['InstanceID'] = "SATA_GOLD1" + storageSystem['InstanceID'] = "SYMMETRIX+00019870000" + volume['volume_metadata'] = [metadata] + volume['name'] = "test-volume" + external_ref = {'source-name': '0123'} + utils = self.driver.common.utils + gbSize = 2 + utils.get_volume_size = mock.Mock( + return_value=gbSize * units.Gi) + utils.get_associated_replication_from_source_volume = mock.Mock( + return_value=None) + utils.get_assoc_pool_from_volume = mock.Mock( + return_value=(poolInstanceName)) + + vol = EMC_StorageVolume() + vol['CreationClassName'] = 'Symm_StorageVolume' + vol['ElementName'] = 'OS-' + volume['name'] + vol['DeviceID'] = external_ref['source-name'] + vol['SystemName'] = storageSystem['InstanceID'] + vol['SystemCreationClassName'] = 'Symm_StorageSystem' + vol.path = vol + utils.rename_volume = mock.Mock( + return_value=vol) + common = self.driver.common + common._initial_setup = mock.Mock( + return_value={'volume_backend_name': 'FCNoFAST', + 'storagetype:fastpolicy': None}) + common._get_pool_and_storage_system = mock.Mock( + return_value=(poolInstanceName, storageSystem)) + volumeInstanceName = {'CreationClassName': "Symm_StorageVolume", + 'DeviceID': "0123", + 'SystemName': "12345"} + utils.find_volume_by_device_id_on_array = mock.Mock( + return_value=volumeInstanceName) + masking = self.driver.common.masking + masking.get_masking_view_from_storage_group = mock.Mock( + return_value=None) + self.driver.manage_existing(volume, external_ref) + utils.rename_volume.assert_called_once_with( + common.conn, volumeInstanceName, volume['name']) + + def test_unmanage_no_fast_success(self): + keybindings = {'CreationClassName': u'Symm_StorageVolume', + 'SystemName': u'SYMMETRIX+000195900000', + 'DeviceID': u'1', + 'SystemCreationClassName': u'Symm_StorageSystem'} + provider_location = {'classname': 'Symm_StorageVolume', + 'keybindings': keybindings} + + volume = {'name': 'vol1', + 'size': 1, + 'id': '1', + 'device_id': '1', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': 'abc', + 'provider_location': six.text_type(provider_location), + 'status': 'available', + 'host': self.data.fake_host, + 'NumberOfBlocks': 100, + 'BlockSize': 512 + } + common = self.driver.common + common._initial_setup = mock.Mock( + return_value={'volume_backend_name': 'FCNoFAST', + 'storagetype:fastpolicy': None}) + utils = self.driver.common.utils + utils.rename_volume = mock.Mock(return_value=None) + self.driver.unmanage(volume) + utils.rename_volume.assert_called_once_with( + common.conn, common._find_lun(volume), '1') + + def test_unmanage_no_fast_failed(self): + keybindings = {'CreationClassName': u'Symm_StorageVolume', + 'SystemName': u'SYMMETRIX+000195900000', + 'DeviceID': u'999', + 'SystemCreationClassName': u'Symm_StorageSystem'} + provider_location = {'classname': 'Symm_StorageVolume', + 'keybindings': keybindings} + + volume = {'name': 'NO_SUCH_VOLUME', + 'size': 1, + 'id': '999', + 'device_id': '999', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'No such volume', + 'display_description': 'volume not on the array', + 'volume_type_id': 'abc', + 'provider_location': six.text_type(provider_location), + 'status': 'available', + 'host': self.data.fake_host, + 'NumberOfBlocks': 100, + 'BlockSize': 512 + } + common = self.driver.common + common._initial_setup = mock.Mock( + return_value={'volume_backend_name': 'FCNoFAST', + 'fastpolicy': None}) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.unmanage, + volume) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -4601,6 +4826,26 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.data.test_volume, EMCVMAXCommonData.test_source_volume) + def test_manage_existing_fast_failed(self): + volume = {} + metadata = {'key': 'array', + 'value': '12345'} + poolInstanceName = {} + storageSystem = {} + poolInstanceName['InstanceID'] = "SATA_GOLD1" + storageSystem['InstanceID'] = "SYMMETRIX+00019870000" + volume['volume_metadata'] = [metadata] + volume['name'] = "test-volume" + external_ref = {'source-name': '0123'} + common = self.driver.common + common._initial_setup = mock.Mock( + return_value={'volume_backend_name': 'FCFAST', + 'storagetype:fastpolicy': 'GOLD'}) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.manage_existing, + volume, + external_ref) + def _cleanup(self): bExists = os.path.exists(self.config_file_path) if bExists: @@ -5135,7 +5380,6 @@ class EMCV3DriverTestCase(test.TestCase): common._create_v3_volume = ( mock.Mock(return_value=(0L, volumeDict, self.data.storage_system))) conn = self.fake_ecom_connection() - storageConfigService = [] storageConfigService = {} storageConfigService['SystemName'] = EMCVMAXCommonData.storage_system storageConfigService['CreationClassName'] = \ diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index aadf3184b..ae61c3a57 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -18,6 +18,8 @@ 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 @@ -3886,3 +3888,165 @@ class EMCVMAXCommon(object): extraSpecs[FASTPOLICY], extraSpecs) return rc + + def manage_existing(self, volume, external_ref): + """Manages an existing VMAX Volume (import to Cinder). + + Renames the existing volume to match the expected name for the volume. + Also need to consider things like QoS, Emulation, account/tenant. + + :param volume: the volume object including the volume_type_id + :param external_ref: reference to the existing volume + :returns: dict -- model_update + :raises: VolumeBackendAPIException + """ + extraSpecs = self._initial_setup(volume) + self.conn = self._get_ecom_connection() + arrayName, deviceId = self.utils.get_array_and_device_id(volume, + external_ref) + + # Manage existing volume is not supported if fast enabled. + if extraSpecs[FASTPOLICY]: + LOG.warning(_LW( + "FAST is enabled. Policy: %(fastPolicyName)s."), + {'fastPolicyName': extraSpecs[FASTPOLICY]}) + exceptionMessage = (_( + "Manage volume is not supported if FAST is enable. " + "FAST policy: %(fastPolicyName)s.") + % {'fastPolicyName': extraSpecs[FASTPOLICY]}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + # Check if the volume is attached by checking if in any masking view. + volumeInstanceName = ( + self.utils.find_volume_by_device_id_on_array(self.conn, + arrayName, deviceId)) + sgInstanceNames = ( + self.utils.get_storage_groups_from_volume( + self.conn, volumeInstanceName)) + + for sgInstanceName in sgInstanceNames: + mvInstanceName = self.masking.get_masking_view_from_storage_group( + self.conn, sgInstanceName) + if mvInstanceName: + exceptionMessage = (_( + "Unable to import volume %(deviceId)s to cinder. " + "Volume is in masking view %(mv)s.") + % {'deviceId': deviceId, + 'mv': mvInstanceName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # Check if there is any associated snapshots with the volume. + cinderPoolInstanceName, storageSystemName = ( + self._get_pool_and_storage_system(extraSpecs)) + repSessionInstanceName = ( + self.utils.get_associated_replication_from_source_volume( + self.conn, storageSystemName, deviceId)) + if repSessionInstanceName: + exceptionMessage = (_( + "Unable to import volume %(deviceId)s to cinder. " + "It is the source volume of replication session %(sync)s.") + % {'deviceId': deviceId, + 'sync': repSessionInstanceName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # Make sure the existing external volume is in the same storage pool. + volumePoolInstanceName = ( + self.utils.get_assoc_pool_from_volume(self.conn, + volumeInstanceName)) + volumePoolName = volumePoolInstanceName['InstanceID'] + cinderPoolName = cinderPoolInstanceName['InstanceID'] + LOG.debug("Storage pool of existing volume: %(volPool)s, " + "Storage pool currently managed by cinder: %(cinderPool)s.", + {'volPool': volumePoolName, + 'cinderPool': cinderPoolName}) + if volumePoolName != cinderPoolName: + exceptionMessage = (_( + "Unable to import volume %(deviceId)s to cinder. The external " + "volume is not in the pool managed by current cinder host.") + % {'deviceId': deviceId}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # Rename the volume + volumeId = volume['name'] + volumeElementName = self.utils.get_volume_element_name(volumeId) + LOG.debug("Rename volume %(vol)s to %(elementName)s.", + {'vol': volumeInstanceName, + 'elementName': volumeElementName}) + volumeInstance = self.utils.rename_volume(self.conn, + volumeInstanceName, + volumeElementName) + keys = {} + volpath = volumeInstance.path + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + + model_update = {} + provider_location = {} + provider_location['classname'] = volpath['CreationClassName'] + provider_location['keybindings'] = keys + + model_update.update({'display_name': volumeElementName}) + volume['provider_location'] = six.text_type(provider_location) + model_update.update({'provider_location': volume['provider_location']}) + return model_update + + def manage_existing_get_size(self, volume, external_ref): + """Return size of an existing VMAX volume to manage_existing. + + :param self: reference to class + :param volume: the volume object including the volume_type_id + :param external_ref: reference to the existing volume + :returns: size of the volume in GB + """ + LOG.debug("Volume in manage_existing_get_size: %(volume)s.", + {'volume': volume}) + arrayName, deviceId = self.utils.get_array_and_device_id(volume, + external_ref) + volumeInstanceName = ( + self.utils.find_volume_by_device_id_on_array(self.conn, + arrayName, deviceId)) + volumeInstance = self.conn.GetInstance(volumeInstanceName) + byteSize = self.utils.get_volume_size(self.conn, volumeInstance) + gbSize = int(byteSize) / units.Gi + LOG.debug( + "Size of volume %(deviceID)s is %(volumeSize)s GB.", + {'deviceID': deviceId, + 'volumeSize': gbSize}) + return gbSize + + def unmanage(self, volume): + """Export VMAX volume from Cinder. + + Leave the volume intact on the backend array. + + :param volume: the volume object + :raises: VolumeBackendAPIException + """ + volumeName = volume['name'] + volumeId = volume['id'] + LOG.debug("Unmanage volume %(name)s, id=%(id)s", + {'name': volumeName, + 'id': volumeId}) + self._initial_setup(volume) + self.conn = self._get_ecom_connection() + volumeInstance = self._find_lun(volume) + if volumeInstance is None: + exceptionMessage = (_("Cannot find Volume: %(id)s. " + "unmanage operation. Exiting...") + % {'id': volumeId}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + # Rename the volume to volumeId, thus remove the 'OS-' prefix. + volumeInstance = self.utils.rename_volume(self.conn, + volumeInstance, + volumeId) diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 0889dc35d..728df347f 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -37,9 +37,10 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): 2.1.1 - Fixed issue with mismatched config (bug #1442376) 2.1.2 - Clean up failed clones (bug #1440154) 2.1.3 - Fixed a problem with FAST support (bug #1435069) + 2.2.0 - Add manage/unmanage """ - VERSION = "2.1.3" + VERSION = "2.2.0" def __init__(self, *args, **kwargs): @@ -320,3 +321,28 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): def delete_cgsnapshot(self, context, cgsnapshot): """Deletes a cgsnapshot.""" return self.common.delete_cgsnapshot(context, cgsnapshot, self.db) + + def manage_existing(self, volume, external_ref): + """Manages an existing VMAX Volume (import to Cinder). + + Renames the Volume to match the expected name for the volume. + Also need to consider things like QoS, Emulation, account/tenant. + """ + return self.common.manage_existing(volume, external_ref) + + def manage_existing_get_size(self, volume, external_ref): + """Return size of an existing VMAX volume to manage_existing. + + :param self: reference to class + :param volume: the volume object including the volume_type_id + :param external_ref: reference to the existing volume + :returns: size of the volume in GB + """ + return self.common.manage_existing_get_size(volume, external_ref) + + def unmanage(self, volume): + """Export VMAX volume from Cinder. + + Leave the volume intact on the backend array. + """ + return self.common.unmanage(volume) diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index da99a150d..148c03d09 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -45,9 +45,10 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): 2.1.1 - Fixed issue with mismatched config (bug #1442376) 2.1.2 - Clean up failed clones (bug #1440154) 2.1.3 - Fixed a problem with FAST support (bug #1435069) + 2.2.0 - Add manage/unmanage """ - VERSION = "2.1.3" + VERSION = "2.2.0" def __init__(self, *args, **kwargs): @@ -326,3 +327,27 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): if 'iscsi_ip_address' in open(CINDER_CONF).read(): return True return False + + def manage_existing(self, volume, external_ref): + """Manages an existing VMAX Volume (import to Cinder). + + Renames the Volume to match the expected name for the volume. + Also need to consider things like QoS, Emulation, account/tenant. + """ + return self.common.manage_existing(volume, external_ref) + + def manage_existing_get_size(self, volume, external_ref): + """Return size of an existing VMAX volume to manage_existing. + + :param self: reference to class + :param volume: the volume object including the volume_type_id + :param external_ref: reference to the existing volume + :returns: size of the volume in GB + """ + return self.common.manage_existing_get_size(volume, external_ref) + + def unmanage(self, volume): + """Export VMAX volume from Cinder, leave the volume intact on the + backend array. + """ + return self.common.unmanage(volume) diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index e63638ac9..1daccc678 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -51,6 +51,7 @@ INTERVAL_10_SEC = 10 INTERVAL = 'storagetype:interval' RETRIES = 'storagetype:retries' CIM_ERR_NOT_FOUND = 6 +VOLUME_ELEMENT_NAME_PREFIX = 'OS-' class EMCVMAXUtils(object): @@ -1973,3 +1974,134 @@ class EMCVMAXUtils(object): if hardwareTypeId == 0: LOG.warning(_LW("Cannot determine the hardware type.")) return hardwareTypeId + + def find_volume_by_device_id_on_array(self, conn, storageSystem, deviceID): + """Find the volume by device ID on a specific array. + + :param conn: connection to the ecom server + :param storageSystem: the storage system name + :param deviceID: string value of the volume device ID + :returns: foundVolumeInstanceName + """ + foundVolumeInstanceName = None + volumeInstanceNames = conn.EnumerateInstanceNames( + 'CIM_StorageVolume') + for volumeInstanceName in volumeInstanceNames: + if storageSystem not in volumeInstanceName['SystemName']: + continue + if deviceID == volumeInstanceName['DeviceID']: + foundVolumeInstanceName = volumeInstanceName + LOG.debug("Found volume: %(vol)s", + {'vol': foundVolumeInstanceName}) + break + if foundVolumeInstanceName is None: + exceptionMessage = (_("Volume %(deviceID)s not found.") + % {'deviceID': deviceID}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + return foundVolumeInstanceName + + def get_volume_element_name(self, volumeId): + """Get volume element name follows naming convention, i.e. 'OS-UUID'. + + :param volumeId: volume id containing uuid + :returns: volume element name in format of OS-UUID + """ + elementName = volumeId + uuid_regex = (re.compile( + '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}', + re.I)) + match = uuid_regex.search(volumeId) + if match: + volumeUUID = match.group() + elementName = ("%(prefix)s%(volumeUUID)s" + % {'prefix': VOLUME_ELEMENT_NAME_PREFIX, + 'volumeUUID': volumeUUID}) + LOG.debug( + "get_volume_element_name elementName: %(elementName)s.", + {'elementName': elementName}) + return elementName + + def rename_volume(self, conn, volume, newName): + """Change the volume ElementName to specified new name. + + :param conn: connection to the ecom server + :param volume: the volume instance name or volume instance + :param newName: new ElementName of the volume + :returns: volumeInstance after rename + """ + if type(volume) is pywbem.cim_obj.CIMInstance: + volumeInstance = volume + else: + volumeInstance = conn.GetInstance(volume) + volumeInstance['ElementName'] = newName + + LOG.debug("Rename volume to new ElementName %(newName)s.", + {'newName': newName}) + + conn.ModifyInstance(volumeInstance, PropertyList=['ElementName']) + + return volumeInstance + + def get_array_and_device_id(self, volume, external_ref): + """Helper function for manage volume to get array name and device ID. + + :param volume: volume object from API + :param external_ref: the existing volume object to be manged + :returns: string value of the array name and device ID + """ + deviceId = external_ref.get(u'source-name', None) + arrayName = '' + for metadata in volume['volume_metadata']: + if metadata['key'].lower() == 'array': + arrayName = metadata['value'] + break + + if deviceId: + LOG.debug("Get device ID of existing volume - device ID: " + "%(deviceId)s, Array: %(arrayName)s.", + {'deviceId': deviceId, + 'arrayName': arrayName}) + else: + exception_message = (_("Source volume device ID is required.")) + raise exception.VolumeBackendAPIException( + data=exception_message) + return (arrayName, deviceId) + + def get_associated_replication_from_source_volume( + self, conn, storageSystem, sourceDeviceId): + """Given the source volume device ID, find associated replication + storage synchronized instance names. + + :param conn: connection to the ecom server + :param storageSystem: the storage system name + :param source: target volume object + :returns: foundSyncName (String) + """ + foundSyncInstanceName = None + syncInstanceNames = conn.EnumerateInstanceNames( + 'SE_StorageSynchronized_SV_SV') + for syncInstanceName in syncInstanceNames: + sourceVolume = syncInstanceName['SystemElement'] + if storageSystem != sourceVolume['SystemName']: + continue + if sourceVolume['DeviceID'] == sourceDeviceId: + # Check that it hasn't recently been deleted. + try: + conn.GetInstance(syncInstanceName) + foundSyncInstanceName = syncInstanceName + LOG.debug("Found sync Name: " + "%(syncName)s.", + {'syncName': foundSyncInstanceName}) + except Exception: + foundSyncInstanceName = None + break + + if foundSyncInstanceName is None: + LOG.info(_LI( + "No replication synchronization session found associated " + "with source volume %(source)s on %(storageSystem)s."), + {'source': sourceDeviceId, 'storageSystem': storageSystem}) + + return foundSyncInstanceName -- 2.45.2