]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
EMC VMAX Manage/Unmanage Volume
authorXing Yang <xing.yang@emc.com>
Tue, 12 May 2015 21:40:27 +0000 (17:40 -0400)
committerXing Yang <xing.yang@emc.com>
Fri, 29 May 2015 13:57:17 +0000 (09:57 -0400)
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
cinder/volume/drivers/emc/emc_vmax_common.py
cinder/volume/drivers/emc/emc_vmax_fc.py
cinder/volume/drivers/emc/emc_vmax_iscsi.py
cinder/volume/drivers/emc/emc_vmax_utils.py

index f141a010c80424cdb58d95c9eb89108cdfc0e35f..9ca427cb0810f474bc0f5608bddd22234ddbabee 100644 (file)
@@ -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'] = \
index aadf3184b6b2b12b4803415170cc5f0ed9202bd3..ae61c3a5789b78d8241f49096cf8ad0322fd31ef 100644 (file)
@@ -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)
index 0889dc35dc8750b5fd5abe86c542c47d5a7591c9..728df347ff4e8feadd0f30aee88be95596c1fa7d 100644 (file)
@@ -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)
index da99a150d2a987aaa84b8e95589613c7b0bb32b8..148c03d09241df2a75779214937ff8784cbed0c0 100644 (file)
@@ -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)
index e63638ac9df187e683636d7d2f3c0dfe1fbdcfdf..1daccc678b38718464f820842e66fc9ffaaa20e9 100644 (file)
@@ -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