]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Update EMC SMI-S Driver
authorXing Yang <xing.yang@emc.com>
Wed, 6 Feb 2013 18:32:12 +0000 (13:32 -0500)
committerXing Yang <xing.yang@emc.com>
Fri, 15 Feb 2013 16:28:32 +0000 (11:28 -0500)
1. Merged with Avishay's changes(https://review.openstack.org/#/c/
19808/3) and removed copy volume<->image from my driver.
2. Also did some refactoring based on Mike's comments
last time (See https://review.openstack.org/#/c/19979/).
Made the following changes in emc_smis_common:
- Added a member variable conn to save get_ecom_connection
and moved exceptions inside the helper function get_ecom_connection.
- Also moved exceptions inside find_pool and get_storage_type.
- Added test cases so get_ecom_connection, find_pool, and
get_storage_type will be covered.
3. Made changes in emc_smis_common to handle both iscsi and fc.

Change-Id: I56db3ba41215489f6afe840ce5310a31f28bf5ae

cinder/tests/test_emc.py
cinder/volume/driver.py
cinder/volume/drivers/emc/emc_smis_common.py
cinder/volume/drivers/emc/emc_smis_iscsi.py

index 4e431cbde77313b1382094c7773051a7ca030fa9..6452b92323d6bc0dc9126541e392fc675381c113 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
+from xml.dom.minidom import Document
+
+from cinder import exception
+from cinder import flags
 from cinder.openstack.common import log as logging
 from cinder import test
 from cinder.volume.drivers.emc.emc_smis_common import EMCSMISCommon
 from cinder.volume.drivers.emc.emc_smis_iscsi import EMCSMISISCSIDriver
 
+FLAGS = flags.FLAGS
+
 LOG = logging.getLogger(__name__)
 
+config_file_name = 'cinder_emc_config.xml'
 storage_system = 'CLARiiON+APM00123456789'
+storage_system_vmax = 'SYMMETRIX+000195900551'
 lunmaskctrl_id = 'CLARiiON+APM00123456789+00aa11bb22cc33dd44ff55gg66hh77ii88jj'
 initiator1 = 'iqn.1993-08.org.debian:01:1a2b3c4d5f6g'
 stconf_service_creationclass = 'Clar_StorageConfigurationService'
@@ -44,6 +53,15 @@ test_volume = {'name': 'vol1',
                'display_name': 'vol1',
                'display_description': 'test volume',
                'volume_type_id': None}
+test_failed_volume = {'name': 'failed_vol',
+                      'size': 1,
+                      'volume_name': 'failed_vol',
+                      'id': '4',
+                      'provider_auth': None,
+                      'project_id': 'project',
+                      'display_name': 'failed_vol',
+                      'display_description': 'test failed volume',
+                      'volume_type_id': None}
 test_snapshot = {'name': 'snapshot1',
                  'size': 1,
                  'id': '4444',
@@ -68,12 +86,67 @@ test_clone3 = {'name': 'clone3',
                'display_name': 'clone3',
                'display_description': 'cloned volume',
                'volume_type_id': None}
+test_snapshot_vmax = {'name': 'snapshot_vmax',
+                      'size': 1,
+                      'id': '4445',
+                      'volume_name': 'vol1',
+                      'volume_size': 1,
+                      'project_id': 'project'}
+failed_snapshot_replica = {'name': 'failed_snapshot_replica',
+                           'size': 1,
+                           'volume_name': 'vol1',
+                           'id': '5',
+                           'provider_auth': None,
+                           'project_id': 'project',
+                           'display_name': 'vol1',
+                           'display_description': 'failed snapshot replica',
+                           'volume_type_id': None}
+failed_snapshot_sync = {'name': 'failed_snapshot_sync',
+                        'size': 1,
+                        'volume_name': 'vol1',
+                        'id': '6',
+                        'provider_auth': None,
+                        'project_id': 'project',
+                        'display_name': 'failed_snapshot_sync',
+                        'display_description': 'failed snapshot sync',
+                        'volume_type_id': None}
+failed_clone_replica = {'name': 'failed_clone_replica',
+                        'size': 1,
+                        'volume_name': 'vol1',
+                        'id': '7',
+                        'provider_auth': None,
+                        'project_id': 'project',
+                        'display_name': 'vol1',
+                        'display_description': 'failed clone replica',
+                        'volume_type_id': None}
+failed_clone_sync = {'name': 'failed_clone_sync',
+                     'size': 1,
+                     'volume_name': 'vol1',
+                     'id': '8',
+                     'provider_auth': None,
+                     'project_id': 'project',
+                     'display_name': 'vol1',
+                     'display_description': 'failed clone sync',
+                     'volume_type_id': None}
+failed_delete_vol = {'name': 'failed_delete_vol',
+                     'size': 1,
+                     'volume_name': 'failed_delete_vol',
+                     'id': '99999',
+                     'provider_auth': None,
+                     'project_id': 'project',
+                     'display_name': 'failed delete vol',
+                     'display_description': 'failed delete volume',
+                     'volume_type_id': None}
 
 
 class EMC_StorageVolume(dict):
     pass
 
 
+class SE_ConcreteJob(dict):
+    pass
+
+
 class FakeEcomConnection():
 
     def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None,
@@ -84,8 +157,43 @@ class FakeEcomConnection():
                      LUNames=None, InitiatorPortIDs=None, DeviceAccesses=None,
                      ProtocolControllers=None,
                      MaskingGroup=None, Members=None):
+
         rc = 0L
-        job = {'status': 'success'}
+        myjob = SE_ConcreteJob()
+        myjob.classname = 'SE_ConcreteJob'
+        myjob['InstanceID'] = '9999'
+        myjob['status'] = 'success'
+        if ElementName == 'failed_vol' and \
+                MethodName == 'CreateOrModifyElementFromStoragePool':
+            rc = 10L
+            myjob['status'] = 'failure'
+        elif ElementName == 'failed_snapshot_replica' and \
+                MethodName == 'CreateElementReplica':
+            rc = 10L
+            myjob['status'] = 'failure'
+        elif Synchronization and \
+                Synchronization['SyncedElement']['ElementName'] \
+                == 'failed_snapshot_sync' and \
+                MethodName == 'ModifyReplicaSynchronization':
+            rc = 10L
+            myjob['status'] = 'failure'
+        elif ElementName == 'failed_clone_replica' and \
+                MethodName == 'CreateElementReplica':
+            rc = 10L
+            myjob['status'] = 'failure'
+        elif Synchronization and \
+                Synchronization['SyncedElement']['ElementName'] \
+                == 'failed_clone_sync' and \
+                MethodName == 'ModifyReplicaSynchronization':
+            rc = 10L
+            myjob['status'] = 'failure'
+        elif TheElements and \
+                TheElements[0]['DeviceID'] == '99999' and \
+                MethodName == 'EMCReturnToStoragePool':
+            rc = 10L
+            myjob['status'] = 'failure'
+
+        job = {'Job': myjob}
         return rc, job
 
     def EnumerateInstanceNames(self, name):
@@ -114,8 +222,21 @@ class FakeEcomConnection():
             result = self._default_enum()
         return result
 
+    def EnumerateInstances(self, name):
+        result = None
+        if name == 'EMC_VirtualProvisioningPool':
+            result = self._enum_pool_details()
+        elif name == 'EMC_UnifiedStoragePool':
+            result = self._enum_pool_details()
+        else:
+            result = self._default_enum()
+        return result
+
     def GetInstance(self, objectpath, LocalOnly=False):
-        name = objectpath['CreationClassName']
+        try:
+            name = objectpath['CreationClassName']
+        except KeyError:
+            name = objectpath.classname
         result = None
         if name == 'Clar_StorageVolume':
             result = self._getinstance_storagevolume(objectpath)
@@ -123,6 +244,8 @@ class FakeEcomConnection():
             result = self._getinstance_unit(objectpath)
         elif name == 'Clar_LunMaskingSCSIProtocolController':
             result = self._getinstance_lunmask()
+        elif name == 'SE_ConcreteJob':
+            result = self._getinstance_job(objectpath)
         else:
             result = self._default_getinstance(objectpath)
         return result
@@ -231,6 +354,19 @@ class FakeEcomConnection():
 
         return unit
 
+    def _getinstance_job(self, jobpath):
+        jobinstance = {}
+        jobinstance['InstanceID'] = '9999'
+        if jobpath['status'] == 'failure':
+            jobinstance['JobState'] = 10
+            jobinstance['ErrorCode'] = 99
+            jobinstance['ErrorDescription'] = 'Failure'
+        else:
+            jobinstance['JobState'] = 7
+            jobinstance['ErrorCode'] = 0
+            jobinstance['ErrorDescription'] = ''
+        return jobinstance
+
     def _default_getinstance(self, objectpath):
         return objectpath
 
@@ -266,6 +402,16 @@ class FakeEcomConnection():
         pools.append(pool)
         return pools
 
+    def _enum_pool_details(self):
+        pools = []
+        pool = {}
+        pool['InstanceID'] = storage_system + '+U+' + storage_type
+        pool['CreationClassName'] = 'Clar_UnifiedStoragePool'
+        pool['TotalManagedSpace'] = 12345678
+        pool['RemainingManagedSpace'] = 123456
+        pools.append(pool)
+        return pools
+
     def _enum_storagevolumes(self):
         vols = []
         vol = EMC_StorageVolume()
@@ -300,6 +446,58 @@ class FakeEcomConnection():
         clone_vol3.path = {'DeviceID': clone_vol3['DeviceID']}
         vols.append(clone_vol3)
 
+        snap_vol_vmax = EMC_StorageVolume()
+        snap_vol_vmax['CreationClassName'] = 'Symm_StorageVolume'
+        snap_vol_vmax['ElementName'] = test_snapshot_vmax['name']
+        snap_vol_vmax['DeviceID'] = test_snapshot_vmax['id']
+        snap_vol_vmax['SystemName'] = storage_system_vmax
+        snap_vol_vmax.path = {'DeviceID': snap_vol_vmax['DeviceID']}
+        vols.append(snap_vol_vmax)
+
+        failed_snap_replica = EMC_StorageVolume()
+        failed_snap_replica['CreationClassName'] = 'Clar_StorageVolume'
+        failed_snap_replica['ElementName'] = failed_snapshot_replica['name']
+        failed_snap_replica['DeviceID'] = failed_snapshot_replica['id']
+        failed_snap_replica['SystemName'] = storage_system
+        failed_snap_replica.path = {
+            'DeviceID': failed_snap_replica['DeviceID']}
+        vols.append(failed_snap_replica)
+
+        failed_snap_sync = EMC_StorageVolume()
+        failed_snap_sync['CreationClassName'] = 'Clar_StorageVolume'
+        failed_snap_sync['ElementName'] = failed_snapshot_sync['name']
+        failed_snap_sync['DeviceID'] = failed_snapshot_sync['id']
+        failed_snap_sync['SystemName'] = storage_system
+        failed_snap_sync.path = {
+            'DeviceID': failed_snap_sync['DeviceID']}
+        vols.append(failed_snap_sync)
+
+        failed_clone_rep = EMC_StorageVolume()
+        failed_clone_rep['CreationClassName'] = 'Clar_StorageVolume'
+        failed_clone_rep['ElementName'] = failed_clone_replica['name']
+        failed_clone_rep['DeviceID'] = failed_clone_replica['id']
+        failed_clone_rep['SystemName'] = storage_system
+        failed_clone_rep.path = {
+            'DeviceID': failed_clone_rep['DeviceID']}
+        vols.append(failed_clone_rep)
+
+        failed_clone_s = EMC_StorageVolume()
+        failed_clone_s['CreationClassName'] = 'Clar_StorageVolume'
+        failed_clone_s['ElementName'] = failed_clone_sync['name']
+        failed_clone_s['DeviceID'] = failed_clone_sync['id']
+        failed_clone_s['SystemName'] = storage_system
+        failed_clone_s.path = {
+            'DeviceID': failed_clone_s['DeviceID']}
+        vols.append(failed_clone_s)
+
+        failed_delete_vol = EMC_StorageVolume()
+        failed_delete_vol['CreationClassName'] = 'Clar_StorageVolume'
+        failed_delete_vol['ElementName'] = 'failed_delete_vol'
+        failed_delete_vol['DeviceID'] = '99999'
+        failed_delete_vol['SystemName'] = storage_system
+        failed_delete_vol.path = {'DeviceID': failed_delete_vol['DeviceID']}
+        vols.append(failed_delete_vol)
+
         return vols
 
     def _enum_syncsvsvs(self):
@@ -331,6 +529,28 @@ class FakeEcomConnection():
         sync3['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
         syncs.append(sync3)
 
+        objpath1 = vols[1]
+        for vol in vols:
+            if vol['ElementName'] == 'failed_snapshot_sync':
+                objpath2 = vol
+                break
+        sync4 = {}
+        sync4['SyncedElement'] = objpath2
+        sync4['SystemElement'] = objpath1
+        sync4['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
+        syncs.append(sync4)
+
+        objpath1 = vols[0]
+        for vol in vols:
+            if vol['ElementName'] == 'failed_clone_sync':
+                objpath2 = vol
+                break
+        sync5 = {}
+        sync5['SyncedElement'] = objpath2
+        sync5['SystemElement'] = objpath1
+        sync5['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
+        syncs.append(sync5)
+
         return syncs
 
     def _enum_unitnames(self):
@@ -357,14 +577,51 @@ class EMCSMISISCSIDriverTestCase(test.TestCase):
 
     def setUp(self):
         super(EMCSMISISCSIDriverTestCase, self).setUp()
-        driver = EMCSMISISCSIDriver()
-        self.driver = driver
+        self.config_file_path = None
+        self.create_fake_config_file()
+        FLAGS.cinder_emc_config_file = self.config_file_path
         self.stubs.Set(EMCSMISISCSIDriver, '_get_iscsi_properties',
                        self.fake_get_iscsi_properties)
         self.stubs.Set(EMCSMISCommon, '_get_ecom_connection',
                        self.fake_ecom_connection)
-        self.stubs.Set(EMCSMISCommon, '_get_storage_type',
-                       self.fake_storage_type)
+        driver = EMCSMISISCSIDriver()
+        self.driver = driver
+
+    def create_fake_config_file(self):
+        doc = Document()
+        emc = doc.createElement("EMC")
+        doc.appendChild(emc)
+
+        storagetype = doc.createElement("StorageType")
+        storagetypetext = doc.createTextNode("gold")
+        emc.appendChild(storagetype)
+        storagetype.appendChild(storagetypetext)
+
+        ecomserverip = doc.createElement("EcomServerIp")
+        ecomserveriptext = doc.createTextNode("1.1.1.1")
+        emc.appendChild(ecomserverip)
+        ecomserverip.appendChild(ecomserveriptext)
+
+        ecomserverport = doc.createElement("EcomServerPort")
+        ecomserverporttext = doc.createTextNode("10")
+        emc.appendChild(ecomserverport)
+        ecomserverport.appendChild(ecomserverporttext)
+
+        ecomusername = doc.createElement("EcomUserName")
+        ecomusernametext = doc.createTextNode("user")
+        emc.appendChild(ecomusername)
+        ecomusername.appendChild(ecomusernametext)
+
+        ecompassword = doc.createElement("EcomPassword")
+        ecompasswordtext = doc.createTextNode("pass")
+        emc.appendChild(ecompassword)
+        ecompassword.appendChild(ecompasswordtext)
+
+        dir_path = os.getcwd()
+        self.config_file_path = dir_path + '/' + config_file_name
+        f = open(self.config_file_path, 'w')
+        doc.writexml(f)
+        f.close()
 
     def fake_ecom_connection(self):
         conn = FakeEcomConnection()
@@ -388,8 +645,8 @@ class EMCSMISISCSIDriverTestCase(test.TestCase):
         LOG.info(_("Fake ISCSI properties: %s") % (properties))
         return properties
 
-    def fake_storage_type(self, filename=None):
-        return storage_type
+    def test_get_volume_stats(self):
+        self.driver.get_volume_stats(True)
 
     def test_create_destroy(self):
         self.driver.create_volume(test_volume)
@@ -417,3 +674,72 @@ class EMCSMISISCSIDriverTestCase(test.TestCase):
         self.driver.terminate_connection(test_volume, connector)
         self.driver.remove_export(None, test_volume)
         self.driver.delete_volume(test_volume)
+
+    def test_create_volume_failed(self):
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume,
+                          test_failed_volume)
+
+    def test_create_volume_snapshot_unsupported(self):
+        self.driver.create_volume(test_volume)
+        self.driver.create_snapshot(test_snapshot_vmax)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          test_clone,
+                          test_snapshot_vmax)
+        self.driver.delete_snapshot(test_snapshot_vmax)
+        self.driver.delete_volume(test_volume)
+
+    def test_create_volume_snapshot_replica_failed(self):
+        self.driver.create_volume(test_volume)
+        self.driver.create_snapshot(test_snapshot)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          failed_snapshot_replica,
+                          test_snapshot)
+        self.driver.delete_snapshot(test_snapshot)
+        self.driver.delete_volume(test_volume)
+
+    def test_create_volume_snapshot_sync_failed(self):
+        self.driver.create_volume(test_volume)
+        self.driver.create_snapshot(test_snapshot)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_volume_from_snapshot,
+                          failed_snapshot_sync,
+                          test_snapshot)
+        self.driver.delete_snapshot(test_snapshot)
+        self.driver.delete_volume(test_volume)
+
+    def test_create_volume_clone_replica_failed(self):
+        self.driver.create_volume(test_volume)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_cloned_volume,
+                          failed_clone_replica,
+                          test_volume)
+        self.driver.delete_volume(test_volume)
+
+    def test_create_volume_clone_sync_failed(self):
+        self.driver.create_volume(test_volume)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.create_cloned_volume,
+                          failed_clone_sync,
+                          test_volume)
+        self.driver.delete_volume(test_volume)
+
+    def test_delete_volume_notfound(self):
+        notfound_delete_vol = {}
+        notfound_delete_vol['name'] = 'notfound_delete_vol'
+        notfound_delete_vol['id'] = '10'
+        self.driver.delete_volume(notfound_delete_vol)
+
+    def test_delete_volume_failed(self):
+        self.driver.create_volume(failed_delete_vol)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.delete_volume,
+                          failed_delete_vol)
+
+    def TearDown(self):
+        bExists = os.path.exists(self.config_file_path)
+        if bExists:
+            os.remove(self.config_file_path)
+        super(EMCSMISISCSIDriverTestCase, self).tearDown()
index 8b63424b01f12981fcf728c0a2a767c904a6fcf6..ca115e1dc9410e91448bd9b40dbe9beb4f71eac8 100644 (file)
@@ -58,6 +58,7 @@ volume_opts = [
 
 FLAGS = flags.FLAGS
 FLAGS.register_opts(volume_opts)
+FLAGS.import_opt('iscsi_helper', 'cinder.volume.iscsi')
 
 
 class VolumeDriver(object):
index 74b75409fb04fbb8c07a10deffb45723cfc53992..e34a34e2c576040e8677589e70a1541e948df6a5 100644 (file)
@@ -19,7 +19,7 @@
 Common class for SMI-S based EMC volume drivers.
 
 This common class is for EMC volume drivers based on SMI-S.
-It supports ISCSI and FC protocols on VNX and VMAX/VMAXe arrays.
+It supports VNX and VMAX arrays.
 
 """
 
@@ -45,7 +45,7 @@ CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml'
 
 
 class EMCSMISCommon():
-    """Common code used by ISCSI and FC drivers."""
+    """Common code that can be used by ISCSI and FC drivers."""
 
     stats = {'driver_version': '1.0',
              'free_capacity_gb': 0,
@@ -55,16 +55,21 @@ class EMCSMISCommon():
              'vendor_name': 'EMC',
              'volume_backend_name': None}
 
-    def __init__(self):
+    def __init__(self, prtcl):
 
         opt = cfg.StrOpt('cinder_emc_config_file',
                          default=CINDER_EMC_CONFIG_FILE,
                          help='use this file for cinder emc plugin '
                          'config data')
         FLAGS.register_opt(opt)
+        self.protocol = prtcl
+        ip, port = self._get_ecom_server()
+        self.user, self.passwd = self._get_ecom_cred()
+        self.url = 'http://' + ip + ':' + port
+        self.conn = self._get_ecom_connection()
 
     def create_volume(self, volume):
-        """Creates a EMC(VMAX/VMAXe/VNX) volume. """
+        """Creates a EMC(VMAX/VNX) volume."""
 
         LOG.debug(_('Entering create_volume.'))
         volumesize = int(volume['size']) * 1073741824
@@ -74,22 +79,9 @@ class EMCSMISCommon():
                  % {'volume': volumename,
                     'size': volumesize})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Error Create Volume: %(volumename)s. "
-                                 "Cannot connect to ECOM server.")
-                                 % {'volumename': volumename})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
+        self.conn = self._get_ecom_connection()
 
         storage_type = self._get_storage_type()
-        if storage_type is None:
-            exception_message = (_("Error Create Volume: %(volumename)s. "
-                                 "Storage type %(storage_type)s not found.")
-                                 % {'volumename': volumename,
-                                    'storage_type': storage_type})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
 
         LOG.debug(_('Create Volume: %(volume)s  '
                   'Storage type: %(storage_type)s')
@@ -97,22 +89,6 @@ class EMCSMISCommon():
                      'storage_type': storage_type})
 
         pool, storage_system = self._find_pool(storage_type)
-        if pool is None:
-            exception_message = (_("Error Create Volume: %(volumename)s. "
-                                 "Pool %(storage_type)s not found.")
-                                 % {'volumename': volumename,
-                                    'storage_type': storage_type})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
-
-        if storage_system is None:
-            exception_message = (_("Error Create Volume: %(volumename)s. "
-                                 "Storage system not found for pool "
-                                 "%(storage_type)s.")
-                                 % {'volumename': volumename,
-                                    'storage_type': storage_type})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
 
         LOG.debug(_('Create Volume: %(volume)s  Pool: %(pool)s  '
                   'Storage System: %(storage_system)s')
@@ -140,7 +116,7 @@ class EMCSMISCommon():
                      'pool': str(pool),
                      'size': volumesize})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'CreateOrModifyElementFromStoragePool',
                     configservice, ElementName=volumename, InPool=pool,
                     ElementType=self._getnum(5, '16'),
@@ -178,16 +154,7 @@ class EMCSMISCommon():
                  % {'volumename': volumename,
                     'snapshotname': snapshotname})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_('Error Create Volume from Snapshot: '
-                                 'Volume: %(volumename)s  Snapshot: '
-                                 '%(snapshotname)s. Cannot connect to'
-                                 ' ECOM server.')
-                                 % {'volumename': volumename,
-                                    'snapshotname': snapshotname})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
+        self.conn = self._get_ecom_connection()
 
         snapshot_instance = self._find_lun(snapshot)
         storage_system = snapshot_instance['SystemName']
@@ -234,7 +201,7 @@ class EMCSMISCommon():
                      'sourceelement': str(snapshot_instance.path)})
 
         # Create a Clone from snapshot
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'CreateElementReplica', repservice,
                     ElementName=volumename,
                     SyncType=self._getnum(8, '16'),
@@ -276,7 +243,7 @@ class EMCSMISCommon():
                      'service': str(repservice),
                      'sync_name': str(sync_name)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'ModifyReplicaSynchronization',
                     repservice,
                     Operation=self._getnum(8, '16'),
@@ -322,16 +289,7 @@ class EMCSMISCommon():
                  % {'volumename': volumename,
                     'srcname': srcname})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_('Error Create Cloned Volume: '
-                                 'Volume: %(volumename)s  Source Volume: '
-                                 '%(srcname)s. Cannot connect to'
-                                 ' ECOM server.')
-                                 % {'volumename': volumename,
-                                    'srcname': srcname})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
+        self.conn = self._get_ecom_connection()
 
         src_instance = self._find_lun(src_vref)
         storage_system = src_instance['SystemName']
@@ -366,8 +324,8 @@ class EMCSMISCommon():
                      'elementname': volumename,
                      'sourceelement': str(src_instance.path)})
 
-        # Create a Clone from snapshot
-        rc, job = conn.InvokeMethod(
+        # Create a Clone from source volume
+        rc, job = self.conn.InvokeMethod(
                     'CreateElementReplica', repservice,
                     ElementName=volumename,
                     SyncType=self._getnum(8, '16'),
@@ -409,7 +367,7 @@ class EMCSMISCommon():
                      'service': str(repservice),
                      'sync_name': str(sync_name)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'ModifyReplicaSynchronization',
                     repservice,
                     Operation=self._getnum(8, '16'),
@@ -450,6 +408,8 @@ class EMCSMISCommon():
         LOG.info(_('Delete Volume: %(volume)s')
                  % {'volume': volumename})
 
+        self.conn = self._get_ecom_connection()
+
         vol_instance = self._find_lun(volume)
         if vol_instance is None:
             LOG.error(_('Volume %(name)s not found on the array. '
@@ -457,14 +417,6 @@ class EMCSMISCommon():
                       % {'name': volumename})
             return
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Error Delete Volume: %(volumename)s. "
-                                 "Cannot connect to ECOM server.")
-                                 % {'volumename': volumename})
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
-
         storage_system = vol_instance['SystemName']
 
         configservice = self._find_storage_configuration_service(
@@ -488,7 +440,7 @@ class EMCSMISCommon():
                      'name': volumename,
                      'vol_instance': str(vol_instance.path)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'EMCReturnToStoragePool',
                     configservice, TheElements=[vol_instance.path])
 
@@ -519,11 +471,7 @@ class EMCSMISCommon():
                  % {'snapshot': snapshotname,
                     'volume': volumename})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            LOG.error(_('Cannot connect to ECOM server.'))
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
+        self.conn = self._get_ecom_connection()
 
         volume = {}
         volume['name'] = volumename
@@ -555,7 +503,7 @@ class EMCSMISCommon():
                      'elementname': snapshotname,
                      'sourceelement': str(vol_instance.path)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'CreateElementReplica', repservice,
                     ElementName=snapshotname,
                     SyncType=self._getnum(7, '16'),
@@ -592,10 +540,7 @@ class EMCSMISCommon():
                  % {'snapshot': snapshotname,
                     'volume': volumename})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
+        self.conn = self._get_ecom_connection()
 
         LOG.debug(_('Delete Snapshot: %(snapshot)s: volume: %(volume)s. '
                   'Finding StorageSychronization_SV_SV.')
@@ -630,7 +575,7 @@ class EMCSMISCommon():
                      'service': str(repservice),
                      'sync_name': str(sync_name)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'ModifyReplicaSynchronization',
                     repservice,
                     Operation=self._getnum(19, '16'),
@@ -663,23 +608,9 @@ class EMCSMISCommon():
                      'snapshotname': snapshotname,
                      'rc': rc})
 
-    def _iscsi_location(ip, target, iqn, lun=None):
-        return "%s:%s,%s %s %s" % (ip, FLAGS.iscsi_port, target, iqn, lun)
-
-    def ensure_export(self, context, volume):
-        """Driver entry point to get the export info for an existing volume."""
-        vol_instance = self._find_lun(volume)
-        device_id = vol_instance['DeviceID']
-        volumename = volume['name']
-        LOG.debug(_('ensure_export: Volume: %(volume)s  Device ID: '
-                  '%(device_id)s')
-                  % {'volume': volumename,
-                     'device_id': device_id})
-
-        return {'provider_location': device_id}
-
     def create_export(self, context, volume):
         """Driver entry point to get the export info for a new volume."""
+        self.conn = self._get_ecom_connection()
         volumename = volume['name']
         LOG.info(_('Create export: %(volume)s')
                  % {'volume': volumename})
@@ -694,7 +625,8 @@ class EMCSMISCommon():
         return {'provider_location': device_id}
 
     # Mapping method for VNX
-    def _expose_paths(self, conn, configservice, vol_instance, connector):
+    def _expose_paths(self, configservice, vol_instance,
+                      connector):
         """This method maps a volume to a host.
 
         It adds a volume and initiator to a Storage Group
@@ -702,7 +634,7 @@ class EMCSMISCommon():
         """
         volumename = vol_instance['ElementName']
         lun_name = vol_instance['DeviceID']
-        initiator = self._find_initiator_name(connector)
+        initiators = self._find_initiator_names(connector)
         storage_system = vol_instance['SystemName']
         lunmask_ctrl = self._find_lunmasking_scsi_protocol_controller(
             storage_system, connector)
@@ -713,20 +645,20 @@ class EMCSMISCommon():
                   % {'vol': str(vol_instance.path),
                      'service': str(configservice),
                      'lun_name': lun_name,
-                     'initiator': initiator})
+                     'initiator': initiators})
 
         if lunmask_ctrl is None:
-            rc, controller = conn.InvokeMethod(
+            rc, controller = self.conn.InvokeMethod(
                                 'ExposePaths',
                                 configservice, LUNames=[lun_name],
-                                InitiatorPortIDs=[initiator],
+                                InitiatorPortIDs=initiators,
                                 DeviceAccesses=[self._getnum(2, '16')])
         else:
             LOG.debug(_('ExposePaths parameter '
                       'LunMaskingSCSIProtocolController: '
                       '%(lunmasking)s')
                       % {'lunmasking': str(lunmask_ctrl)})
-            rc, controller = conn.InvokeMethod(
+            rc, controller = self.conn.InvokeMethod(
                                 'ExposePaths',
                                 configservice, LUNames=[lun_name],
                                 DeviceAccesses=[self._getnum(2, '16')],
@@ -741,8 +673,11 @@ class EMCSMISCommon():
                   % volumename)
 
     # Unmapping method for VNX
-    def _hide_paths(self, conn, configservice, vol_instance, connector):
-        """Removes a volume from the Storage Group
+    def _hide_paths(self, configservice, vol_instance,
+                    connector):
+        """This method unmaps a volume from the host.
+
+        Removes a volume from the Storage Group
         and therefore unmaps the volume from the host.
         """
         volumename = vol_instance['ElementName']
@@ -758,7 +693,7 @@ class EMCSMISCommon():
                      'device_id': device_id,
                      'lunmasking': str(lunmask_ctrl)})
 
-        rc, controller = conn.InvokeMethod(
+        rc, controller = self.conn.InvokeMethod(
             'HidePaths', configservice,
             LUNames=[device_id], ProtocolControllers=[lunmask_ctrl])
 
@@ -770,10 +705,13 @@ class EMCSMISCommon():
         LOG.debug(_('HidePaths for volume %s completed successfully.')
                   % volumename)
 
-    # Mapping method for VMAX/VMAXe
-    def _add_members(self, conn, configservice, vol_instance, connector):
-        """Add volume to the Device Masking Group that belongs to
-        a Masking View"""
+    # Mapping method for VMAX
+    def _add_members(self, configservice, vol_instance):
+        """This method maps a volume to a host.
+
+        Add volume to the Device Masking Group that belongs to
+        a Masking View.
+        """
         volumename = vol_instance['ElementName']
         masking_group = self._find_device_masking_group()
 
@@ -783,7 +721,7 @@ class EMCSMISCommon():
                      'masking_group': str(masking_group),
                      'vol': str(vol_instance.path)})
 
-        rc, job = conn.InvokeMethod(
+        rc, job = self.conn.InvokeMethod(
                     'AddMembers', configservice,
                     MaskingGroup=masking_group, Members=[vol_instance.path])
 
@@ -798,9 +736,13 @@ class EMCSMISCommon():
         LOG.debug(_('AddMembers for volume %s completed successfully.')
                   % volumename)
 
-    # Unmapping method for VMAX/VMAXe
-    def _remove_members(self, conn, configservice, vol_instance, connector):
-        """Removes an export for a volume."""
+    # Unmapping method for VMAX
+    def _remove_members(self, configservice, vol_instance):
+        """This method unmaps a volume from a host.
+
+        Removes volume from the Device Masking Group that belongs to
+        a Masking View.
+        """
         volumename = vol_instance['ElementName']
         masking_group = self._find_device_masking_group()
 
@@ -810,9 +752,9 @@ class EMCSMISCommon():
                      'masking_group': str(masking_group),
                      'vol': str(vol_instance.path)})
 
-        rc, job = conn.InvokeMethod('RemoveMembers', configservice,
-                                    MaskingGroup=masking_group,
-                                    Members=[vol_instance.path])
+        rc, job = self.conn.InvokeMethod('RemoveMembers', configservice,
+                                         MaskingGroup=masking_group,
+                                         Members=[vol_instance.path])
 
         if rc != 0L:
             rc, errordesc = self._wait_for_job_complete(job)
@@ -831,11 +773,6 @@ class EMCSMISCommon():
         LOG.info(_('Map volume: %(volume)s')
                  % {'volume': volumename})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
-
         vol_instance = self._find_lun(volume)
         storage_system = vol_instance['SystemName']
 
@@ -849,9 +786,9 @@ class EMCSMISCommon():
 
         isVMAX = storage_system.find('SYMMETRIX')
         if isVMAX > -1:
-            self._add_members(conn, configservice, vol_instance, connector)
+            self._add_members(configservice, vol_instance)
         else:
-            self._expose_paths(conn, configservice, vol_instance, connector)
+            self._expose_paths(configservice, vol_instance, connector)
 
     def _unmap_lun(self, volume, connector):
         """Unmaps a volume from the host."""
@@ -859,12 +796,8 @@ class EMCSMISCommon():
         LOG.info(_('Unmap volume: %(volume)s')
                  % {'volume': volumename})
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
-
-        device_number = self.find_device_number(volume)
+        device_info = self.find_device_number(volume)
+        device_number = device_info['hostlunid']
         if device_number is None:
             LOG.info(_("Volume %s is not mapped. No volume to unmap.")
                      % (volumename))
@@ -883,44 +816,43 @@ class EMCSMISCommon():
 
         isVMAX = storage_system.find('SYMMETRIX')
         if isVMAX > -1:
-            self._remove_members(conn, configservice, vol_instance, connector)
+            self._remove_members(configservice, vol_instance)
         else:
-            self._hide_paths(conn, configservice, vol_instance, connector)
+            self._hide_paths(configservice, vol_instance, connector)
 
     def initialize_connection(self, volume, connector):
         """Initializes the connection and returns connection info."""
         volumename = volume['name']
         LOG.info(_('Initialize connection: %(volume)s')
                  % {'volume': volumename})
-        device_number = self.find_device_number(volume)
+        self.conn = self._get_ecom_connection()
+        device_info = self.find_device_number(volume)
+        device_number = device_info['hostlunid']
         if device_number is not None:
             LOG.info(_("Volume %s is already mapped.")
                      % (volumename))
         else:
             self._map_lun(volume, connector)
+            # Find host lun id again after the volume is exported to the host
+            device_info = self.find_device_number(volume)
+
+        return device_info
 
     def terminate_connection(self, volume, connector):
-        """Disallow connection from connector"""
+        """Disallow connection from connector."""
         volumename = volume['name']
         LOG.info(_('Terminate connection: %(volume)s')
                  % {'volume': volumename})
+        self.conn = self._get_ecom_connection()
         self._unmap_lun(volume, connector)
 
     def update_volume_status(self):
         """Retrieve status info."""
         LOG.debug(_("Updating volume status"))
-
+        self.conn = self._get_ecom_connection()
         storage_type = self._get_storage_type()
-        if storage_type is None:
-            exception_message = (_("Storage type name not found."))
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
 
         pool, storagesystem = self._find_pool(storage_type, True)
-        if pool is None:
-            exception_message = (_("Error finding pool details."))
-            LOG.error(exception_message)
-            raise exception.VolumeBackendAPIException(data=exception_message)
 
         self.stats['total_capacity_gb'] = pool['TotalManagedSpace']
         self.stats['free_capacity_gb'] = pool['RemainingManagedSpace']
@@ -928,8 +860,7 @@ class EMCSMISCommon():
         return self.stats
 
     def _get_storage_type(self, filename=None):
-        """Get the storage type from the config file
-        """
+        """Get the storage type from the config file."""
         if filename == None:
             filename = FLAGS.cinder_emc_config_file
 
@@ -945,8 +876,9 @@ class EMCSMISCommon():
             LOG.debug(_("Found Storage Type: %s") % (storageType))
             return storageType
         else:
-            LOG.debug(_("Storage Type not found."))
-            return None
+            exception_message = (_("Storage type not found."))
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
     def _get_masking_view(self, filename=None):
         if filename == None:
@@ -1012,18 +944,18 @@ class EMCSMISCommon():
             return None
 
     def _get_ecom_connection(self, filename=None):
-        ip, port = self._get_ecom_server()
-        user, passwd = self._get_ecom_cred()
-        url = 'http://' + ip + ':' + port
-        conn = pywbem.WBEMConnection(url, (user, passwd),
+        conn = pywbem.WBEMConnection(self.url, (self.user, self.passwd),
                                      default_namespace='root/emc')
+        if conn is None:
+            exception_message = (_("Cannot connect to ECOM server"))
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
         return conn
 
     def _find_replication_service(self, storage_system):
         foundRepService = None
-        conn = self._get_ecom_connection()
-        repservices = conn.EnumerateInstanceNames('EMC_ReplicationService')
+        repservices = self.conn.EnumerateInstanceNames(
+            'EMC_ReplicationService')
         for repservice in repservices:
             if storage_system == repservice['SystemName']:
                 foundRepService = repservice
@@ -1035,8 +967,7 @@ class EMCSMISCommon():
 
     def _find_storage_configuration_service(self, storage_system):
         foundConfigService = None
-        conn = self._get_ecom_connection()
-        configservices = conn.EnumerateInstanceNames(
+        configservices = self.conn.EnumerateInstanceNames(
             'EMC_StorageConfigurationService')
         for configservice in configservices:
             if storage_system == configservice['SystemName']:
@@ -1049,8 +980,7 @@ class EMCSMISCommon():
 
     def _find_controller_configuration_service(self, storage_system):
         foundConfigService = None
-        conn = self._get_ecom_connection()
-        configservices = conn.EnumerateInstanceNames(
+        configservices = self.conn.EnumerateInstanceNames(
             'EMC_ControllerConfigurationService')
         for configservice in configservices:
             if storage_system == configservice['SystemName']:
@@ -1061,39 +991,68 @@ class EMCSMISCommon():
 
         return foundConfigService
 
+    def _find_storage_hardwareid_service(self, storage_system):
+        foundConfigService = None
+        configservices = self.conn.EnumerateInstanceNames(
+            'EMC_StorageHardwareIDManagementService')
+        for configservice in configservices:
+            if storage_system == configservice['SystemName']:
+                foundConfigService = configservice
+                LOG.debug(_("Found Storage Hardware ID Management Service: %s")
+                          % (str(configservice)))
+                break
+
+        return foundConfigService
+
     # Find pool based on storage_type
     def _find_pool(self, storage_type, details=False):
         foundPool = None
         systemname = None
-        conn = self._get_ecom_connection()
         # Only get instance names if details flag is False;
         # Otherwise get the whole instances
         if details is False:
-            vpools = conn.EnumerateInstanceNames('EMC_VirtualProvisioningPool')
-            upools = conn.EnumerateInstanceNames('EMC_UnifiedStoragePool')
+            vpools = self.conn.EnumerateInstanceNames(
+                'EMC_VirtualProvisioningPool')
+            upools = self.conn.EnumerateInstanceNames(
+                'EMC_UnifiedStoragePool')
         else:
-            vpools = conn.EnumerateInstances('EMC_VirtualProvisioningPool')
-            upools = conn.EnumerateInstances('EMC_UnifiedStoragePool')
+            vpools = self.conn.EnumerateInstances(
+                'EMC_VirtualProvisioningPool')
+            upools = self.conn.EnumerateInstances(
+                'EMC_UnifiedStoragePool')
 
         for upool in upools:
             poolinstance = upool['InstanceID']
             # Example: CLARiiON+APM00115204878+U+Pool 0
             poolname, systemname = self._parse_pool_instance_id(poolinstance)
             if poolname is not None and systemname is not None:
-                if storage_type == poolname:
+                if str(storage_type) == str(poolname):
                     foundPool = upool
                     break
-        if foundPool is not None and systemname is not None:
-            return foundPool, systemname
 
-        for vpool in vpools:
-            poolinstance = vpool['InstanceID']
-            # Example: SYMMETRIX+000195900551+TP+Sol_Innov
-            poolname, systemname = self._parse_pool_instance_id(poolinstance)
-            if poolname is not None and systemname is not None:
-                if storage_type == poolname:
-                    foundPool = vpool
-                    break
+        if foundPool is None:
+            for vpool in vpools:
+                poolinstance = vpool['InstanceID']
+                # Example: SYMMETRIX+000195900551+TP+Sol_Innov
+                poolname, systemname = self._parse_pool_instance_id(
+                    poolinstance)
+                if poolname is not None and systemname is not None:
+                    if str(storage_type) == str(poolname):
+                        foundPool = vpool
+                        break
+
+        if foundPool is None:
+            exception_message = (_("Pool %(storage_type)s is not found.")
+                                 % {'storage_type': storage_type})
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        if systemname is None:
+            exception_message = (_("Storage system not found for pool "
+                                 "%(storage_type)s.")
+                                 % {'storage_type': storage_type})
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
 
         LOG.debug(_("Pool: %(pool)s  SystemName: %(systemname)s.")
                   % {'pool': str(foundPool), 'systemname': systemname})
@@ -1123,21 +1082,20 @@ class EMCSMISCommon():
             device_id = None
 
         volumename = volume['name']
-        conn = self._get_ecom_connection()
 
-        names = conn.EnumerateInstanceNames('EMC_StorageVolume')
+        names = self.conn.EnumerateInstanceNames('EMC_StorageVolume')
 
         for n in names:
             if device_id is not None:
                 if n['DeviceID'] == device_id:
-                    vol_instance = conn.GetInstance(n)
+                    vol_instance = self.conn.GetInstance(n)
                     foundinstance = vol_instance
                     break
                 else:
                     continue
 
             else:
-                vol_instance = conn.GetInstance(n)
+                vol_instance = self.conn.GetInstance(n)
                 if vol_instance['ElementName'] == volumename:
                     foundinstance = vol_instance
                     volume['provider_location'] = foundinstance['DeviceID']
@@ -1161,18 +1119,17 @@ class EMCSMISCommon():
         LOG.debug(_("Source: %(volumename)s  Target: %(snapshotname)s.")
                   % {'volumename': volumename, 'snapshotname': snapshotname})
 
-        conn = self._get_ecom_connection()
-
-        names = conn.EnumerateInstanceNames('SE_StorageSynchronized_SV_SV')
+        names = self.conn.EnumerateInstanceNames(
+            'SE_StorageSynchronized_SV_SV')
 
         for n in names:
-            snapshot_instance = conn.GetInstance(n['SyncedElement'],
-                                                 LocalOnly=False)
+            snapshot_instance = self.conn.GetInstance(n['SyncedElement'],
+                                                      LocalOnly=False)
             if snapshotname != snapshot_instance['ElementName']:
                 continue
 
-            vol_instance = conn.GetInstance(n['SystemElement'],
-                                            LocalOnly=False)
+            vol_instance = self.conn.GetInstance(n['SystemElement'],
+                                                 LocalOnly=False)
             if vol_instance['ElementName'] == volumename:
                 foundsyncname = n
                 storage_system = vol_instance['SystemName']
@@ -1190,27 +1147,34 @@ class EMCSMISCommon():
                          'sync': str(foundsyncname)})
         return foundsyncname, storage_system
 
-    def _find_initiator_name(self, connector):
-        """ Get initiator name from connector['initiator']
-        """
-        foundinitiatorname = None
-        if connector['initiator']:
-            foundinitiatorname = connector['initiator']
+    def _find_initiator_names(self, connector):
+        foundinitiatornames = []
+        iscsi = 'iscsi'
+        fc = 'fc'
+        name = 'initiator name'
+        if self.protocol.lower() == iscsi and connector['initiator']:
+            foundinitiatornames.append(connector['initiator'])
+        elif self.protocol.lower() == fc and connector['wwpns']:
+            for wwn in connector['wwpns']:
+                foundinitiatornames.append(wwn)
+            name = 'world wide port names'
+
+        if foundinitiatornames is None or len(foundinitiatornames) == 0:
+            msg = (_('Error finding %s.') % name)
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
 
-        LOG.debug(_("Initiator name: %(initiator)s.")
-                  % {'initiator': foundinitiatorname})
-        return foundinitiatorname
+        LOG.debug(_("Found %(name)s: %(initiator)s.")
+                  % {'name': name,
+                     'initiator': foundinitiatornames})
+        return foundinitiatornames
 
     def _wait_for_job_complete(self, job):
         jobinstancename = job['Job']
 
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
-
         while True:
-            jobinstance = conn.GetInstance(jobinstancename, LocalOnly=False)
+            jobinstance = self.conn.GetInstance(jobinstancename,
+                                                LocalOnly=False)
             jobstate = jobinstance['JobState']
             # From ValueMap of JobState in CIM_ConcreteJob
             # 2L=New, 3L=Starting, 4L=Running, 32767L=Queue Pending
@@ -1234,22 +1198,27 @@ class EMCSMISCommon():
     def _find_lunmasking_scsi_protocol_controller(self, storage_system,
                                                   connector):
         foundCtrl = None
-        conn = self._get_ecom_connection()
-        initiator = self._find_initiator_name(connector)
-        controllers = conn.EnumerateInstanceNames(
+        initiators = self._find_initiator_names(connector)
+        controllers = self.conn.EnumerateInstanceNames(
             'EMC_LunMaskingSCSIProtocolController')
         for ctrl in controllers:
             if storage_system != ctrl['SystemName']:
                 continue
-            associators = conn.Associators(ctrl,
-                                           resultClass='EMC_StorageHardwareID')
+            associators = self.conn.Associators(
+                            ctrl,
+                            resultClass='EMC_StorageHardwareID')
             for assoc in associators:
                 # if EMC_StorageHardwareID matches the initiator,
                 # we found the existing EMC_LunMaskingSCSIProtocolController
                 # (Storage Group for VNX)
                 # we can use for masking a new LUN
-                if assoc['StorageID'] == initiator:
-                    foundCtrl = ctrl
+                hardwareid = assoc['StorageID']
+                for initiator in initiators:
+                    if hardwareid.lower() == initiator.lower():
+                        foundCtrl = ctrl
+                        break
+
+                if foundCtrl is not None:
                     break
 
             if foundCtrl is not None:
@@ -1259,7 +1228,7 @@ class EMCSMISCommon():
                   "%(storage_system)s and initiator %(initiator)s is  "
                   "%(ctrl)s.")
                   % {'storage_system': storage_system,
-                     'initiator': initiator,
+                     'initiator': initiators,
                      'ctrl': str(foundCtrl)})
         return foundCtrl
 
@@ -1268,14 +1237,13 @@ class EMCSMISCommon():
     def _find_lunmasking_scsi_protocol_controller_for_vol(self, vol_instance,
                                                           connector):
         foundCtrl = None
-        conn = self._get_ecom_connection()
-        initiator = self._find_initiator_name(connector)
-        controllers = conn.AssociatorNames(
+        initiators = self._find_initiator_names(connector)
+        controllers = self.conn.AssociatorNames(
                         vol_instance.path,
                         resultClass='EMC_LunMaskingSCSIProtocolController')
 
         for ctrl in controllers:
-            associators = conn.Associators(
+            associators = self.conn.Associators(
                             ctrl,
                             resultClass='EMC_StorageHardwareID')
             for assoc in associators:
@@ -1283,8 +1251,13 @@ class EMCSMISCommon():
                 # we found the existing EMC_LunMaskingSCSIProtocolController
                 # (Storage Group for VNX)
                 # we can use for masking a new LUN
-                if assoc['StorageID'] == initiator:
-                    foundCtrl = ctrl
+                hardwareid = assoc['StorageID']
+                for initiator in initiators:
+                    if hardwareid.lower() == initiator.lower():
+                        foundCtrl = ctrl
+                        break
+
+                if foundCtrl is not None:
                     break
 
             if foundCtrl is not None:
@@ -1292,10 +1265,49 @@ class EMCSMISCommon():
 
         LOG.debug(_("LunMaskingSCSIProtocolController for storage volume "
                   "%(vol)s and initiator %(initiator)s is  %(ctrl)s.")
-                  % {'vol': str(vol_instance.path), 'initiator': initiator,
+                  % {'vol': str(vol_instance.path), 'initiator': initiators,
                      'ctrl': str(foundCtrl)})
         return foundCtrl
 
+    # Find out how many volumes are mapped to a host
+    # assoociated to the LunMaskingSCSIProtocolController
+    def get_num_volumes_mapped(self, volume, connector):
+        numVolumesMapped = 0
+        volumename = volume['name']
+        vol_instance = self._find_lun(volume)
+        if vol_instance is None:
+            msg = (_('Volume %(name)s not found on the array. '
+                   'Cannot determine if there are volumes mapped.')
+                   % {'name': volumename})
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        storage_system = vol_instance['SystemName']
+
+        ctrl = self._find_lunmasking_scsi_protocol_controller(
+            storage_system,
+            connector)
+
+        LOG.debug(_("LunMaskingSCSIProtocolController for storage system "
+                  "%(storage)s and %(connector)s is %(ctrl)s.")
+                  % {'storage': storage_system,
+                     'connector': connector,
+                     'ctrl': str(ctrl)})
+
+        associators = conn.Associators(
+            ctrl,
+            resultClass='EMC_StorageVolume')
+
+        numVolumesMapped = len(associators)
+
+        LOG.debug(_("Found %(numVolumesMapped)d volumes on storage system "
+                  "%(storage)s mapped to %(initiator)s.")
+                  % {'numVolumesMapped': numVolumesMapped,
+                     'storage': storage_system,
+                     'connector': connector})
+
+        return numVolumesMapped
+
     # Find an available device number that a host can see
     def _find_avail_device_number(self, storage_system):
         out_device_number = '000000'
@@ -1303,8 +1315,7 @@ class EMCSMISCommon():
         numlist = []
         myunitnames = []
 
-        conn = self._get_ecom_connection()
-        unitnames = conn.EnumerateInstanceNames(
+        unitnames = self.conn.EnumerateInstanceNames(
             'CIM_ProtocolControllerForUnit')
         for unitname in unitnames:
             controller = unitname['Antecedent']
@@ -1313,7 +1324,8 @@ class EMCSMISCommon():
             classname = controller['CreationClassName']
             index = classname.find('LunMaskingSCSIProtocolController')
             if index > -1:
-                unitinstance = conn.GetInstance(unitname, LocalOnly=False)
+                unitinstance = self.conn.GetInstance(unitname,
+                                                     LocalOnly=False)
                 numDeviceNumber = int(unitinstance['DeviceNumber'])
                 numlist.append(numDeviceNumber)
                 myunitnames.append(unitname)
@@ -1331,11 +1343,11 @@ class EMCSMISCommon():
     def find_device_number(self, volume):
         out_num_device_number = None
 
-        conn = self._get_ecom_connection()
         volumename = volume['name']
         vol_instance = self._find_lun(volume)
+        storage_system = vol_instance['SystemName']
 
-        unitnames = conn.ReferenceNames(
+        unitnames = self.conn.ReferenceNames(
                         vol_instance.path,
                         ResultClass='CIM_ProtocolControllerForUnit')
 
@@ -1345,14 +1357,16 @@ class EMCSMISCommon():
             index = classname.find('LunMaskingSCSIProtocolController')
             if index > -1:  # VNX
                 # Get an instance of CIM_ProtocolControllerForUnit
-                unitinstance = conn.GetInstance(unitname, LocalOnly=False)
+                unitinstance = self.conn.GetInstance(unitname,
+                                                     LocalOnly=False)
                 numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
                 out_num_device_number = numDeviceNumber
                 break
             else:
                 index = classname.find('Symm_LunMaskingView')
-                if index > -1:  # VMAX/VMAXe
-                    unitinstance = conn.GetInstance(unitname, LocalOnly=False)
+                if index > -1:  # VMAX
+                    unitinstance = self.conn.GetInstance(unitname,
+                                                         LocalOnly=False)
                     numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
                     out_num_device_number = numDeviceNumber
                     break
@@ -1369,27 +1383,29 @@ class EMCSMISCommon():
                        'volumename': volumename,
                        'vol_instance': str(vol_instance.path)})
 
-        return out_num_device_number
+        data = {'hostlunid': out_num_device_number,
+                'storagesystem': storage_system}
+
+        LOG.debug(_("Device info: %(data)s.") % {'data': data})
+
+        return data
 
     def _find_device_masking_group(self):
         """Finds the Device Masking Group in a masking view."""
         foundMaskingGroup = None
         maskingview_name = self._get_masking_view()
-        conn = self._get_ecom_connection()
-        if conn is None:
-            exception_message = (_("Cannot connect to ECOM server"))
-            raise exception.VolumeBackendAPIException(data=exception_message)
 
-        maskingviews = conn.EnumerateInstanceNames(
+        maskingviews = self.conn.EnumerateInstanceNames(
             'EMC_LunMaskingSCSIProtocolController')
         for view in maskingviews:
-            instance = conn.GetInstance(view, LocalOnly=False)
+            instance = self.conn.GetInstance(view, LocalOnly=False)
             if maskingview_name == instance['ElementName']:
                 foundView = view
                 break
 
-        groups = conn.AssociatorNames(foundView,
-                                      ResultClass='SE_DeviceMaskingGroup')
+        groups = self.conn.AssociatorNames(
+            foundView,
+            ResultClass='SE_DeviceMaskingGroup')
         foundMaskingGroup = groups[0]
 
         LOG.debug(_("Masking view: %(view)s DeviceMaskingGroup: %(masking)s.")
@@ -1411,3 +1427,63 @@ class EMCSMISCommon():
             result = num
 
         return result
+
+    # Find target WWNs
+    def get_target_wwns(self, storage_system, connector):
+        target_wwns = []
+
+        configservice = self._find_storage_hardwareid_service(
+            storage_system)
+        if configservice is None:
+            exception_msg = (_("Error finding Storage Hardware ID Service."))
+            LOG.error(exception_msg)
+            raise exception.VolumeBackendAPIException(data=exception_msg)
+
+        hardwareids = self._find_storage_hardwareids(connector)
+
+        LOG.debug(_('EMCGetTargetEndpoints: Service: %(service)s  '
+                  'Storage HardwareIDs: %(hardwareids)s.')
+                  % {'service': str(configservice),
+                     'hardwareids': str(hardwareids)})
+
+        for hardwareid in hardwareids:
+            rc, targetendpoints = self.conn.InvokeMethod(
+                'EMCGetTargetEndpoints',
+                configservice,
+                HardwareId=hardwareid)
+
+            if rc != 0L:
+                msg = (_('Error finding Target WWNs.'))
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+
+            endpoints = targetendpoints['TargetEndpoints']
+            for targetendpoint in endpoints:
+                wwn = targetendpoint['Name']
+                # Add target wwn to the list if it is not already there
+                if not any(d.get('wwn', None) == wwn for d in target_wwns):
+                    target_wwns.append({'wwn': wwn})
+                LOG.debug(_('Add target WWN: %s.') % wwn)
+
+        LOG.debug(_('Target WWNs: %s.') % target_wwns)
+
+        return target_wwns
+
+    # Find Storage Hardware IDs
+    def _find_storage_hardwareids(self, connector):
+        foundInstances = []
+        wwpns = self._find_initiator_names(connector)
+        hardwareids = self.conn.EnumerateInstances(
+            'SE_StorageHardwareID')
+        for hardwareid in hardwareids:
+            storid = hardwareid['StorageID']
+            for wwpn in wwpns:
+                if wwpn.lower() == storid.lower():
+                    foundInstances.append(hardwareid.path)
+
+        LOG.debug(_("Storage Hardware IDs for %(wwpns)s is "
+                  "%(foundInstances)s.")
+                  % {'wwpns': str(wwpns),
+                     'foundInstances': str(foundInstances)})
+
+        return foundInstances
index 936d17e708307c44ddbefd1b4848ee3ea6a43438..955829fec4487c8a3199b100eaa361fe815acdaa 100644 (file)
@@ -16,7 +16,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 """
-ISCSI Drivers for EMC VNX and VMAX/VMAXe arrays based on SMI-S.
+ISCSI Drivers for EMC VNX and VMAX arrays based on SMI-S.
 
 """
 
@@ -35,29 +35,19 @@ LOG = logging.getLogger(__name__)
 FLAGS = flags.FLAGS
 
 
-def get_iscsi_initiator():
-    """Get iscsi initiator name for this machine."""
-    # NOTE openiscsi stores initiator name in a file that
-    #      needs root permission to read.
-    contents = utils.read_file_as_root('/etc/iscsi/initiatorname.iscsi')
-    for l in contents.split('\n'):
-        if l.startswith('InitiatorName='):
-            return l[l.index('=') + 1:].strip()
-
-
 class EMCSMISISCSIDriver(driver.ISCSIDriver):
-    """EMC ISCSI Drivers for VMAX/VMAXe and VNX using SMI-S."""
+    """EMC ISCSI Drivers for VMAX and VNX using SMI-S."""
 
     def __init__(self, *args, **kwargs):
 
         super(EMCSMISISCSIDriver, self).__init__(*args, **kwargs)
-        self.common = emc_smis_common.EMCSMISCommon()
+        self.common = emc_smis_common.EMCSMISCommon('iSCSI')
 
     def check_for_setup_error(self):
         pass
 
     def create_volume(self, volume):
-        """Creates a EMC(VMAX/VMAXe/VNX) volume."""
+        """Creates a EMC(VMAX/VNX) volume."""
         self.common.create_volume(volume)
 
     def create_volume_from_snapshot(self, volume, snapshot):
@@ -80,12 +70,9 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
         """Deletes a snapshot."""
         self.common.delete_snapshot(snapshot)
 
-    def _iscsi_location(ip, target, iqn, lun=None):
-        return "%s:%s,%s %s %s" % (ip, FLAGS.iscsi_port, target, iqn, lun)
-
     def ensure_export(self, context, volume):
         """Driver entry point to get the export info for an existing volume."""
-        return self.common.ensure_export(context, volume)
+        pass
 
     def create_export(self, context, volume):
         """Driver entry point to get the export info for a new volume."""
@@ -102,7 +89,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
     def initialize_connection(self, volume, connector):
         """Initializes the connection and returns connection info.
 
-        the iscsi driver returns a driver_volume_type of 'iscsi'.
+        The iscsi driver returns a driver_volume_type of 'iscsi'.
         the format of the driver data is defined in _get_iscsi_properties.
         Example return value::
 
@@ -134,11 +121,13 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
                                     FLAGS.iscsi_ip_address,
                                     run_as_root=True)
         for target in out.splitlines():
-            return target
+            index = target.find(FLAGS.iscsi_ip_address)
+            if index != -1:
+                return target
         return None
 
     def _get_iscsi_properties(self, volume):
-        """Gets iscsi configuration
+        """Gets iscsi configuration.
 
         We ideally get saved information in the volume entity, but fall back
         to discovery if need be. Discovery may be completely removed in future
@@ -175,12 +164,14 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
         properties['target_portal'] = results[0].split(",")[0]
         properties['target_iqn'] = results[1]
 
-        device_number = self.common.find_device_number(volume)
-        if device_number is None:
+        device_info = self.common.find_device_number(volume)
+        if device_info is None or device_info['hostlunid'] is None:
             exception_message = (_("Cannot find device number for volume %s")
                                  % volume['name'])
             raise exception.VolumeBackendAPIException(data=exception_message)
 
+        device_number = device_info['hostlunid']
+
         properties['target_lun'] = device_number
 
         properties['volume_id'] = volume['id']
@@ -197,98 +188,15 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
 
         return properties
 
-    def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs):
-        check_exit_code = kwargs.pop('check_exit_code', 0)
-        (out, err) = self._execute('iscsiadm', '-m', 'node', '-T',
-                                   iscsi_properties['target_iqn'],
-                                   '-p', iscsi_properties['target_portal'],
-                                   *iscsi_command, run_as_root=True,
-                                   check_exit_code=check_exit_code)
-        LOG.debug("iscsiadm %s: stdout=%s stderr=%s" %
-                  (iscsi_command, out, err))
-        return (out, err)
-
     def terminate_connection(self, volume, connector, **kwargs):
-        """Disallow connection from connector"""
+        """Disallow connection from connector."""
         self.common.terminate_connection(volume, connector)
 
-    def copy_image_to_volume(self, context, volume, image_service, image_id):
-        """Fetch the image from image_service and write it to the volume."""
-        LOG.debug(_('copy_image_to_volume %s.') % volume['name'])
-        initiator = get_iscsi_initiator()
-        connector = {}
-        connector['initiator'] = initiator
-
-        iscsi_properties, volume_path = self._attach_volume(
-            context, volume, connector)
-
-        with utils.temporary_chown(volume_path):
-            with utils.file_open(volume_path, "wb") as image_file:
-                image_service.download(context, image_id, image_file)
-
-        self.terminate_connection(volume, connector)
-
-    def _attach_volume(self, context, volume, connector):
-        """Attach the volume."""
-        iscsi_properties = None
-        host_device = None
-        init_conn = self.initialize_connection(volume, connector)
-        iscsi_properties = init_conn['data']
-
-        self._run_iscsiadm(iscsi_properties, ("--login",),
-                           check_exit_code=[0, 255])
-
-        self._iscsiadm_update(iscsi_properties, "node.startup", "automatic")
-
-        host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
-                       (iscsi_properties['target_portal'],
-                        iscsi_properties['target_iqn'],
-                        iscsi_properties.get('target_lun', 0)))
-
-        tries = 0
-        while not os.path.exists(host_device):
-            if tries >= FLAGS.num_iscsi_scan_tries:
-                raise exception.CinderException(
-                    _("iSCSI device not found at %s") % (host_device))
-
-            LOG.warn(_("ISCSI volume not yet found at: %(host_device)s. "
-                     "Will rescan & retry.  Try number: %(tries)s") %
-                     locals())
-
-            # The rescan isn't documented as being necessary(?), but it helps
-            self._run_iscsiadm(iscsi_properties, ("--rescan",))
-
-            tries = tries + 1
-            if not os.path.exists(host_device):
-                time.sleep(tries ** 2)
-
-        if tries != 0:
-            LOG.debug(_("Found iSCSI node %(host_device)s "
-                      "(after %(tries)s rescans)") %
-                      locals())
-
-        return iscsi_properties, host_device
-
-    def copy_volume_to_image(self, context, volume, image_service, image_meta):
-        """Copy the volume to the specified image."""
-        LOG.debug(_('copy_volume_to_image %s.') % volume['name'])
-        initiator = get_iscsi_initiator()
-        connector = {}
-        connector['initiator'] = initiator
-
-        iscsi_properties, volume_path = self._attach_volume(
-            context, volume, connector)
-
-        with utils.temporary_chown(volume_path):
-            with utils.file_open(volume_path) as volume_file:
-                image_service.update(context, image_meta['id'], {},
-                                     volume_file)
-
-        self.terminate_connection(volume, connector)
-
     def get_volume_stats(self, refresh=False):
         """Get volume status.
-        If 'refresh' is True, run update the stats first."""
+
+        If 'refresh' is True, run update the stats first.
+        """
         if refresh:
             self.update_volume_status()