]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add EMC Volume Driver in Cinder
authorXing Yang <xing.yang@emc.com>
Fri, 14 Dec 2012 05:05:14 +0000 (00:05 -0500)
committerXing Yang <xing.yang@emc.com>
Tue, 25 Dec 2012 22:41:55 +0000 (17:41 -0500)
Add support for EMC storage in the Cinder-Volume service.
This driver is based on the existing ISCSIDriver, with the ability to
create/delete and attach/detach volumes and create/delete snapshots, etc.
The Cinder Driver executes the volume operations by communicating with the
backend EMC storage. It uses a CIM client in python called PyWBEM to make
CIM operations over HTTP.
EMC CIM Object Manager (ECOM) is packaged with the SMI-S Provider. It is a
CIM server that allows CIM clients to make CIM operations over HTTP, using
SMI-S in the backend for EMC storage operations.
SMI-S Provider supports the SNIA Storage Management Initiative (SMI), an
ANSI standard for storage management. It supports VMAX/VMAXe and VNX
storage systems.

Implement bp: emc-volume-driver

Change-Id: Iafce98603d31d66a7297ef11c92d5e6ac6ba3737

cinder/tests/test_emc.py [new file with mode: 0644]
cinder/volume/drivers/emc.py [new file with mode: 0644]
etc/cinder/cinder_emc_config.xml.sample [new file with mode: 0644]

diff --git a/cinder/tests/test_emc.py b/cinder/tests/test_emc.py
new file mode 100644 (file)
index 0000000..14bcc6d
--- /dev/null
@@ -0,0 +1,381 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 EMC Corporation, Inc.
+# Copyright (c) 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume.drivers.emc import EMCISCSIDriver
+
+LOG = logging.getLogger(__name__)
+
+storage_system = 'CLARiiON+APM00123456789'
+lunmaskctrl_id = 'CLARiiON+APM00123456789+00aa11bb22cc33dd44ff55gg66hh77ii88jj'
+initiator1 = 'iqn.1993-08.org.debian:01:1a2b3c4d5f6g'
+stconf_service_creationclass = 'Clar_StorageConfigurationService'
+ctrlconf_service_creationclass = 'Clar_ControllerConfigurationService'
+rep_service_creationclass = 'Clar_ReplicationService'
+vol_creationclass = 'Clar_StorageVolume'
+pool_creationclass = 'Clar_UnifiedStoragePool'
+lunmask_creationclass = 'Clar_LunMaskingSCSIProtocolController'
+unit_creationclass = 'CIM_ProtocolControllerForUnit'
+storage_type = 'gold'
+
+test_volume = {'name': 'vol1',
+               'size': 1,
+               'volume_name': 'vol1',
+               'id': '1',
+               'provider_auth': None,
+               'project_id': 'project',
+               'display_name': 'vol1',
+               'display_description': 'test volume',
+               'volume_type_id': None}
+test_snapshot = {'name': 'snapshot1',
+                 'size': 1,
+                 'id': '4444',
+                 'volume_name': 'vol1',
+                 'volume_size': 1,
+                 'project_id': 'project'}
+test_clone = {'name': 'clone1',
+              'size': 1,
+              'volume_name': 'vol1',
+              'id': '2',
+              'provider_auth': None,
+              'project_id': 'project',
+              'display_name': 'clone1',
+              'display_description': 'volume created from snapshot',
+              'volume_type_id': None}
+
+
+class EMC_StorageVolume(dict):
+    pass
+
+
+class FakeEcomConnection():
+
+    def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None,
+                     ElementType=None, Size=None,
+                     SyncType=None, SourceElement=None,
+                     Operation=None, Synchronization=None,
+                     TheElements=None,
+                     LUNames=None, InitiatorPortIDs=None, DeviceAccesses=None,
+                     ProtocolControllers=None,
+                     MaskingGroup=None, Members=None):
+        rc = 0L
+        job = {'status': 'success'}
+        return rc, job
+
+    def EnumerateInstanceNames(self, name):
+        result = None
+        if name == 'EMC_ReplicationService':
+            result = self._enum_replicationservices()
+        elif name == 'EMC_StorageConfigurationService':
+            result = self._enum_stconfsvcs()
+        elif name == 'EMC_ControllerConfigurationService':
+            result = self._enum_ctrlconfsvcs()
+        elif name == 'EMC_VirtualProvisioningPool':
+            result = self._enum_pools()
+        elif name == 'EMC_UnifiedStoragePool':
+            result = self._enum_pools()
+        elif name == 'EMC_StorageVolume':
+            result = self._enum_storagevolumes()
+        elif name == 'Clar_StorageVolume':
+            result = self._enum_storagevolumes()
+        elif name == 'SE_StorageSynchronized_SV_SV':
+            result = self._enum_syncsvsvs()
+        elif name == 'CIM_ProtocolControllerForUnit':
+            result = self._enum_unitnames()
+        elif name == 'EMC_LunMaskingSCSIProtocolController':
+            result = self._enum_lunmaskctrls()
+        else:
+            result = self._default_enum()
+        return result
+
+    def GetInstance(self, objectpath, LocalOnly=False):
+        name = objectpath['CreationClassName']
+        result = None
+        if name == 'Clar_StorageVolume':
+            result = self._getinstance_storagevolume(objectpath)
+        elif name == 'CIM_ProtocolControllerForUnit':
+            result = self._getinstance_unit(objectpath)
+        elif name == 'Clar_LunMaskingSCSIProtocolController':
+            result = self._getinstance_lunmask()
+        else:
+            result = self._default_getinstance(objectpath)
+        return result
+
+    def Associators(self, objectpath, resultClass='EMC_StorageHardwareID'):
+        result = None
+        if resultClass == 'EMC_StorageHardwareID':
+            result = self._assoc_hdwid()
+        else:
+            result = self._default_assoc(objectpath)
+        return result
+
+    def AssociatorNames(self, objectpath,
+                        resultClass='EMC_LunMaskingSCSIProtocolController'):
+        result = None
+        if resultClass == 'EMC_LunMaskingSCSIProtocolController':
+            result = self._assocnames_lunmaskctrl()
+        else:
+            result = self._default_assocnames(objectpath)
+        return result
+
+    def ReferenceNames(self, objectpath,
+                       ResultClass='CIM_ProtocolControllerForUnit'):
+        result = None
+        if ResultClass == 'CIM_ProtocolControllerForUnit':
+            result = self._ref_unitnames()
+        else:
+            result = self._default_ref(objectpath)
+        return result
+
+    def _ref_unitnames(self):
+        units = []
+        unit = {}
+
+        dependent = {}
+        dependent['CreationClassName'] = vol_creationclass
+        dependent['DeviceID'] = test_volume['id']
+        dependent['ElementName'] = test_volume['name']
+        dependent['SystemName'] = storage_system
+
+        antecedent = {}
+        antecedent['CreationClassName'] = lunmask_creationclass
+        antecedent['DeviceID'] = lunmaskctrl_id
+        antecedent['SystemName'] = storage_system
+
+        unit['Dependent'] = dependent
+        unit['Antecedent'] = antecedent
+        unit['CreationClassName'] = unit_creationclass
+        units.append(unit)
+
+        return units
+
+    def _default_ref(self, objectpath):
+        return objectpath
+
+    def _assoc_hdwid(self):
+        assocs = []
+        assoc = {}
+        assoc['StorageID'] = initiator1
+        assocs.append(assoc)
+        return assocs
+
+    def _default_assoc(self, objectpath):
+        return objectpath
+
+    def _assocnames_lunmaskctrl(self):
+        return self._enum_lunmaskctrls()
+
+    def _default_assocnames(self, objectpath):
+        return objectpath
+
+    def _getinstance_storagevolume(self, objectpath):
+        instance = EMC_StorageVolume()
+        vols = self._enum_storagevolumes()
+        for vol in vols:
+            if vol['DeviceID'] == objectpath['DeviceID']:
+                instance = vol
+                break
+        return instance
+
+    def _getinstance_lunmask(self):
+        lunmask = {}
+        lunmask['CreationClassName'] = lunmask_creationclass
+        lunmask['DeviceID'] = lunmaskctrl_id
+        lunmask['SystemName'] = storage_system
+        return lunmask
+
+    def _getinstance_unit(self, objectpath):
+        unit = {}
+
+        dependent = {}
+        dependent['CreationClassName'] = vol_creationclass
+        dependent['DeviceID'] = test_volume['id']
+        dependent['ElementName'] = test_volume['name']
+        dependent['SystemName'] = storage_system
+
+        antecedent = {}
+        antecedent['CreationClassName'] = lunmask_creationclass
+        antecedent['DeviceID'] = lunmaskctrl_id
+        antecedent['SystemName'] = storage_system
+
+        unit['Dependent'] = dependent
+        unit['Antecedent'] = antecedent
+        unit['CreationClassName'] = unit_creationclass
+        unit['DeviceNumber'] = '0'
+
+        return unit
+
+    def _default_getinstance(self, objectpath):
+        return objectpath
+
+    def _enum_replicationservices(self):
+        rep_services = []
+        rep_service = {}
+        rep_service['SystemName'] = storage_system
+        rep_service['CreationClassName'] = rep_service_creationclass
+        rep_services.append(rep_service)
+        return rep_services
+
+    def _enum_stconfsvcs(self):
+        conf_services = []
+        conf_service = {}
+        conf_service['SystemName'] = storage_system
+        conf_service['CreationClassName'] = stconf_service_creationclass
+        conf_services.append(conf_service)
+        return conf_services
+
+    def _enum_ctrlconfsvcs(self):
+        conf_services = []
+        conf_service = {}
+        conf_service['SystemName'] = storage_system
+        conf_service['CreationClassName'] = ctrlconf_service_creationclass
+        conf_services.append(conf_service)
+        return conf_services
+
+    def _enum_pools(self):
+        pools = []
+        pool = {}
+        pool['InstanceID'] = storage_system + '+U+' + storage_type
+        pool['CreationClassName'] = 'Clar_UnifiedStoragePool'
+        pools.append(pool)
+        return pools
+
+    def _enum_storagevolumes(self):
+        vols = []
+        vol = EMC_StorageVolume()
+        vol['CreationClassName'] = 'Clar_StorageVolume'
+        vol['ElementName'] = test_volume['name']
+        vol['DeviceID'] = test_volume['id']
+        vol['SystemName'] = storage_system
+        vol.path = {'DeviceID': vol['DeviceID']}
+        vols.append(vol)
+
+        snap_vol = EMC_StorageVolume()
+        snap_vol['CreationClassName'] = 'Clar_StorageVolume'
+        snap_vol['ElementName'] = test_snapshot['name']
+        snap_vol['DeviceID'] = test_snapshot['id']
+        snap_vol['SystemName'] = storage_system
+        snap_vol.path = {'DeviceID': snap_vol['DeviceID']}
+        vols.append(snap_vol)
+
+        clone_vol = EMC_StorageVolume()
+        clone_vol['CreationClassName'] = 'Clar_StorageVolume'
+        clone_vol['ElementName'] = test_clone['name']
+        clone_vol['DeviceID'] = test_clone['id']
+        clone_vol['SystemName'] = storage_system
+        clone_vol.path = {'DeviceID': clone_vol['DeviceID']}
+        vols.append(clone_vol)
+
+        return vols
+
+    def _enum_syncsvsvs(self):
+        syncs = []
+        sync = {}
+
+        vols = self._enum_storagevolumes()
+        objpath1 = vols[0]
+        objpath2 = vols[1]
+        sync['SyncedElement'] = objpath2
+        sync['SystemElement'] = objpath1
+        sync['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
+        syncs.append(sync)
+
+        return syncs
+
+    def _enum_unitnames(self):
+        return self._ref_unitnames()
+
+    def _enum_lunmaskctrls(self):
+        ctrls = []
+        ctrl = {}
+        ctrl['CreationClassName'] = lunmask_creationclass
+        ctrl['DeviceID'] = lunmaskctrl_id
+        ctrl['SystemName'] = storage_system
+        ctrls.append(ctrl)
+        return ctrls
+
+    def _default_enum(self):
+        names = []
+        name = {}
+        name['Name'] = 'default'
+        names.append(name)
+        return names
+
+
+class EMCISCSIDriverTestCase(test.TestCase):
+
+    def setUp(self):
+        super(EMCISCSIDriverTestCase, self).setUp()
+        driver = EMCISCSIDriver()
+        self.driver = driver
+        self.stubs.Set(EMCISCSIDriver, '_get_iscsi_properties',
+                       self.fake_get_iscsi_properties)
+        self.stubs.Set(EMCISCSIDriver, '_get_ecom_connection',
+                       self.fake_ecom_connection)
+        self.stubs.Set(EMCISCSIDriver, '_get_storage_type',
+                       self.fake_storage_type)
+
+    def fake_ecom_connection(self):
+        conn = FakeEcomConnection()
+        return conn
+
+    def fake_get_iscsi_properties(self, volume):
+        LOG.info('Fake _get_iscsi_properties.')
+        properties = {}
+        properties['target_discovered'] = True
+        properties['target_portal'] = '10.10.10.10'
+        properties['target_iqn'] = 'iqn.1993-08.org.debian:01:a1b2c3d4e5f6'
+        device_number = '000008'
+        properties['target_lun'] = device_number
+        properties['volume_id'] = volume['id']
+        auth = volume['provider_auth']
+        if auth:
+            (auth_method, auth_username, auth_secret) = auth.split()
+            properties['auth_method'] = auth_method
+            properties['auth_username'] = auth_username
+            properties['auth_password'] = auth_secret
+        LOG.info(_("Fake ISCSI properties: %s") % (properties))
+        return properties
+
+    def fake_storage_type(self, filename=None):
+        return storage_type
+
+    def test_create_destroy(self):
+        self.driver.create_volume(test_volume)
+        self.driver.delete_volume(test_volume)
+
+    def test_create_volume_snapshot_destroy(self):
+        self.driver.create_volume(test_volume)
+        self.driver.create_snapshot(test_snapshot)
+        self.driver.create_volume_from_snapshot(
+            test_clone, test_snapshot)
+        self.driver.delete_volume(test_clone)
+        self.driver.delete_snapshot(test_snapshot)
+        self.driver.delete_volume(test_volume)
+
+    def test_map_unmap(self):
+        self.driver.create_volume(test_volume)
+        export = self.driver.create_export(None, test_volume)
+        test_volume['provider_location'] = export['provider_location']
+        connector = {'initiator': initiator1}
+        connection_info = self.driver.initialize_connection(test_volume,
+                                                            connector)
+        self.driver.terminate_connection(test_volume, connector)
+        self.driver.remove_export(None, test_volume)
+        self.driver.delete_volume(test_volume)
diff --git a/cinder/volume/drivers/emc.py b/cinder/volume/drivers/emc.py
new file mode 100644 (file)
index 0000000..c128364
--- /dev/null
@@ -0,0 +1,1445 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 EMC Corporation, Inc.
+# Copyright (c) 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+Drivers for EMC volumes.
+
+"""
+
+import os
+import time
+from xml.dom.minidom import parseString
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+from cinder.openstack.common import log as logging
+from cinder import utils
+from cinder.volume import driver
+from cinder.volume import volume_types
+
+LOG = logging.getLogger(__name__)
+
+FLAGS = flags.FLAGS
+
+try:
+    import pywbem
+except ImportError:
+    LOG.info(_('Module PyWBEM not installed.  PyWBEM can be downloaded '
+               'from http://sourceforge.net/apps/mediawiki/pywbem'))
+
+CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml'
+
+
+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 EMCISCSIDriver(driver.ISCSIDriver):
+    """Drivers for VMAX/VMAXe and VNX"""
+
+    def __init__(self, *args, **kwargs):
+
+        super(EMCISCSIDriver, self).__init__(*args, **kwargs)
+
+        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)
+
+    def check_for_setup_error(self):
+        pass
+
+    def create_volume(self, volume):
+        """Creates a EMC(VMAX/VMAXe/VNX) volume. """
+
+        LOG.debug(_('Entering create_volume.'))
+        volumesize = int(volume['size']) * 1073741824
+        volumename = volume['name']
+
+        LOG.info(_('Create Volume: %(volume)s  Size: %(size)lu')
+                 % {'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)
+
+        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')
+                  % {'volume': volumename,
+                     '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')
+                  % {'volume': volumename,
+                     'pool': str(pool),
+                     'storage_system': storage_system})
+
+        configservice = self._find_storage_configuration_service(
+                            storage_system)
+        if configservice is None:
+            exception_message = (_("Error Create Volume: %(volumename)s. "
+                                 "Storage Configuration Service 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: %(name)s  Method: '
+                  'CreateOrModifyElementFromStoragePool  ConfigServicie: '
+                  '%(service)s  ElementName: %(name)s  InPool: %(pool)s  '
+                  'ElementType: 5  Size: %(size)lu')
+                  % {'service': str(configservice),
+                     'name': volumename,
+                     'pool': str(pool),
+                     'size': volumesize})
+
+        rc, job = conn.InvokeMethod(
+                    'CreateOrModifyElementFromStoragePool',
+                    configservice, ElementName=volumename, InPool=pool,
+                    ElementType=self._getnum(5, '16'),
+                    Size=self._getnum(volumesize, '64'))
+
+        LOG.debug(_('Create Volume: %(volumename)s  Return code: %(rc)lu')
+                  % {'volumename': volumename,
+                     'rc': rc})
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                LOG.error(_('Error Create Volume: %(volumename)s.  '
+                          'Return code: %(rc)lu.  Error: %(error)s')
+                          % {'volumename': volumename,
+                             'rc': rc,
+                             'error': errordesc})
+                raise exception.VolumeBackendAPIException(data=errordesc)
+
+        LOG.debug(_('Leaving create_volume: %(volumename)s  '
+                  'Return code: %(rc)lu')
+                  % {'volumename': volumename,
+                     'rc': rc})
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        """Creates a volume from a snapshot."""
+
+        LOG.debug(_('Entering create_volume_from_snapshot.'))
+
+        snapshotname = snapshot['name']
+        volumename = volume['name']
+
+        LOG.info(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                 'Snapshot: %(snapshotname)s')
+                 % {'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)
+
+        snapshot_instance = self._find_lun(snapshot)
+        storage_system = snapshot_instance['SystemName']
+
+        LOG.debug(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s  Snapshot Instance: '
+                  '%(snapshotinstance)s  Storage System: %(storage_system)s.')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'snapshotinstance': str(snapshot_instance.path),
+                     'storage_system': storage_system})
+
+        isVMAX = storage_system.find('SYMMETRIX')
+        if isVMAX > -1:
+            exception_message = (_('Error Create Volume from Snapshot: '
+                                 'Volume: %(volumename)s  Snapshot: '
+                                 '%(snapshotname)s. Create Volume '
+                                 'from Snapshot is NOT supported on VMAX.')
+                                 % {'volumename': volumename,
+                                    'snapshotname': snapshotname})
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        repservice = self._find_replication_service(storage_system)
+        if repservice is None:
+            exception_message = (_('Error Create Volume from Snapshot: '
+                                 'Volume: %(volumename)s  Snapshot: '
+                                 '%(snapshotname)s. Cannot find Replication '
+                                 'Service to create volume from snapshot.')
+                                 % {'volumename': volumename,
+                                    'snapshotname': snapshotname})
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        LOG.debug(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s  Method: CreateElementReplica  '
+                  'ReplicationService: %(service)s  ElementName: '
+                  '%(elementname)s  SyncType: 8  SourceElement: '
+                  '%(sourceelement)s')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'service': str(repservice),
+                     'elementname': volumename,
+                     'sourceelement': str(snapshot_instance.path)})
+
+        # Create a Clone from snapshot
+        rc, job = conn.InvokeMethod(
+                    'CreateElementReplica', repservice,
+                    ElementName=volumename,
+                    SyncType=self._getnum(8, '16'),
+                    SourceElement=snapshot_instance.path)
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                exception_message = (_('Error Create Volume from Snapshot: '
+                                     'Volume: %(volumename)s  Snapshot:'
+                                     '%(snapshotname)s.  Return code: %(rc)lu.'
+                                     'Error: %(error)s')
+                                     % {'volumename': volumename,
+                                        'snapshotname': snapshotname,
+                                        'rc': rc,
+                                        'error': errordesc})
+                LOG.error(exception_message)
+                raise exception.VolumeBackendAPIException(
+                    data=exception_message)
+
+        LOG.debug(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s.  Successfully clone volume '
+                  'from snapshot.  Finding the clone relationship.')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname})
+
+        sync_name, storage_system = self._find_storage_sync_sv_sv(
+            volumename, snapshotname)
+
+        # Remove the Clone relationshop so it can be used as a regular lun
+        # 8 - Detach operation
+        LOG.debug(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s.  Remove the clone '
+                  'relationship. Method: ModifyReplicaSynchronization '
+                  'ReplicationService: %(service)s  Operation: 8  '
+                  'Synchronization: %(sync_name)s')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'service': str(repservice),
+                     'sync_name': str(sync_name)})
+
+        rc, job = conn.InvokeMethod(
+                    'ModifyReplicaSynchronization',
+                    repservice,
+                    Operation=self._getnum(8, '16'),
+                    Synchronization=sync_name)
+
+        LOG.debug(_('Create Volume from Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s  Return code: %(rc)lu')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'rc': rc})
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                exception_message = (_('Error Create Volume from Snapshot: '
+                                     'Volume: %(volumename)s  '
+                                     'Snapshot: %(snapshotname)s.  '
+                                     'Return code: %(rc)lu.  Error: %(error)s')
+                                     % {'volumename': volumename,
+                                        'snapshotname': snapshotname,
+                                        'rc': rc,
+                                        'error': errordesc})
+                LOG.error(exception_message)
+                raise exception.VolumeBackendAPIException(
+                    data=exception_message)
+
+        LOG.debug(_('Leaving create_volume_from_snapshot: Volume: '
+                  '%(volumename)s Snapshot: %(snapshotname)s  '
+                  'Return code: %(rc)lu.')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'rc': rc})
+
+    def delete_volume(self, volume):
+        """Deletes an EMC volume."""
+        LOG.debug(_('Entering delete_volume.'))
+        volumename = volume['name']
+        LOG.info(_('Delete Volume: %(volume)s')
+                 % {'volume': volumename})
+
+        vol_instance = self._find_lun(volume)
+        if vol_instance is None:
+            LOG.error(_('Volume %(name)s not found on the array. '
+                      'No volume to delete.')
+                      % {'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(
+                        storage_system)
+        if configservice is None:
+            exception_message = (_("Error Delete Volume: %(volumename)s. "
+                                 "Storage Configuration Service not found.")
+                                 % {'volumename': volumename})
+            LOG.error(exception_message)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        device_id = vol_instance['DeviceID']
+
+        LOG.debug(_('Delete Volume: %(name)s  DeviceID: %(deviceid)s')
+                  % {'name': volumename,
+                     'deviceid': device_id})
+
+        LOG.debug(_('Delete Volume: %(name)s  Method: EMCReturnToStoragePool '
+                  'ConfigServic: %(service)s  TheElement: %(vol_instance)s')
+                  % {'service': str(configservice),
+                     'name': volumename,
+                     'vol_instance': str(vol_instance.path)})
+
+        rc, job = conn.InvokeMethod(
+                    'EMCReturnToStoragePool',
+                    configservice, TheElements=[vol_instance.path])
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                exception_message = (_('Error Delete Volume: %(volumename)s.  '
+                                     'Return code: %(rc)lu.  Error: %(error)s')
+                                     % {'volumename': volumename,
+                                        'rc': rc,
+                                        'error': errordesc})
+                LOG.error(exception_message)
+                raise exception.VolumeBackendAPIException(
+                    data=exception_message)
+
+        LOG.debug(_('Leaving delete_volume: %(volumename)s  Return code: '
+                  '%(rc)lu')
+                  % {'volumename': volumename,
+                     'rc': rc})
+
+    def create_snapshot(self, snapshot):
+        """Creates a snapshot."""
+        LOG.debug(_('Entering create_snapshot.'))
+
+        snapshotname = snapshot['name']
+        volumename = snapshot['volume_name']
+        LOG.info(_('Create snapshot: %(snapshot)s: volume: %(volume)s')
+                 % {'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)
+
+        volume = {}
+        volume['name'] = volumename
+        volume['provider_location'] = None
+        vol_instance = self._find_lun(volume)
+        device_id = vol_instance['DeviceID']
+        storage_system = vol_instance['SystemName']
+        LOG.debug(_('Device ID: %(deviceid)s: Storage System: '
+                  '%(storagesystem)s')
+                  % {'deviceid': device_id,
+                     'storagesystem': storage_system})
+
+        repservice = self._find_replication_service(storage_system)
+        if repservice is None:
+            LOG.error(_("Cannot find Replication Service to create snapshot "
+                      "for volume %s.") % volumename)
+            exception_message = (_("Cannot find Replication Service to "
+                                 "create snapshot for volume %s.")
+                                 % volumename)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        LOG.debug(_("Create Snapshot:  Method: CreateElementReplica: "
+                  "Target: %(snapshot)s  Source: %(volume)s  Replication "
+                  "Service: %(service)s  ElementName: %(elementname)s  Sync "
+                  "Type: 7  SourceElement: %(sourceelement)s.")
+                  % {'snapshot': snapshotname,
+                     'volume': volumename,
+                     'service': str(repservice),
+                     'elementname': snapshotname,
+                     'sourceelement': str(vol_instance.path)})
+
+        rc, job = conn.InvokeMethod(
+                    'CreateElementReplica', repservice,
+                    ElementName=snapshotname,
+                    SyncType=self._getnum(7, '16'),
+                    SourceElement=vol_instance.path)
+
+        LOG.debug(_('Create Snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s  Return code: %(rc)lu')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'rc': rc})
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                exception_message = (_('Error Create Snapshot: (snapshot)s '
+                                     'Volume: %(volume)s Error: %(errordesc)s')
+                                     % {'snapshot': snapshotname, 'volume':
+                                        volumename, 'errordesc': errordesc})
+                LOG.error(exception_message)
+                raise exception.VolumeBackendAPIException(
+                    data=exception_message)
+
+        LOG.debug(_('Leaving create_snapshot: Snapshot: %(snapshot)s '
+                  'Volume: %(volume)s  Return code: %(rc)lu.') %
+                  {'snapshot': snapshotname, 'volume': volumename, 'rc': rc})
+
+    def delete_snapshot(self, snapshot):
+        """Deletes a snapshot."""
+        LOG.debug(_('Entering delete_snapshot.'))
+
+        snapshotname = snapshot['name']
+        volumename = snapshot['volume_name']
+        LOG.info(_('Delete Snapshot: %(snapshot)s: volume: %(volume)s')
+                 % {'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)
+
+        LOG.debug(_('Delete Snapshot: %(snapshot)s: volume: %(volume)s. '
+                  'Finding StorageSychronization_SV_SV.')
+                  % {'snapshot': snapshotname,
+                     'volume': volumename})
+
+        sync_name, storage_system = self._find_storage_sync_sv_sv(
+                                    snapshotname, volumename)
+        if sync_name is None:
+            LOG.error(_('Snapshot: %(snapshot)s: volume: %(volume)s '
+                      'not found on the array. No snapshot to delete.')
+                      % {'snapshot': snapshotname,
+                         'volume': volumename})
+            return
+
+        repservice = self._find_replication_service(storage_system)
+        if repservice is None:
+            exception_message = (_("Cannot find Replication Service to "
+                                 "create snapshot for volume %s.")
+                                 % volumename)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        # Delete snapshot - deletes both the target element
+        # and the snap session
+        LOG.debug(_("Delete Snapshot: Target: %(snapshot)s  "
+                  "Source: %(volume)s.  Method: "
+                  "ModifyReplicaSynchronization:  "
+                  "Replication Service: %(service)s  Operation: 19  "
+                  "Synchronization: %(sync_name)s.")
+                  % {'snapshot': snapshotname,
+                     'volume': volumename,
+                     'service': str(repservice),
+                     'sync_name': str(sync_name)})
+
+        rc, job = conn.InvokeMethod(
+                    'ModifyReplicaSynchronization',
+                    repservice,
+                    Operation=self._getnum(19, '16'),
+                    Synchronization=sync_name)
+
+        LOG.debug(_('Delete Snapshot: Volume: %(volumename)s  Snapshot: '
+                  '%(snapshotname)s  Return code: %(rc)lu')
+                  % {'volumename': volumename,
+                     'snapshotname': snapshotname,
+                     'rc': rc})
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                exception_message = (_('Error Delete Snapshot: Volume: '
+                                     '%(volumename)s  Snapshot: '
+                                     '%(snapshotname)s. Return code: %(rc)lu.'
+                                     ' Error: %(error)s')
+                                     % {'volumename': volumename,
+                                        'snapshotname': snapshotname,
+                                        'rc': rc,
+                                        'error': errordesc})
+                LOG.error(exception_message)
+                raise exception.VolumeBackendAPIException(
+                    data=exception_message)
+
+        LOG.debug(_('Leaving delete_snapshot: Volume: %(volumename)s  '
+                  'Snapshot: %(snapshotname)s  Return code: %(rc)lu.')
+                  % {'volumename': volumename,
+                     '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."""
+        volumename = volume['name']
+        LOG.info(_('Create export: %(volume)s')
+                 % {'volume': volumename})
+        vol_instance = self._find_lun(volume)
+        device_id = vol_instance['DeviceID']
+
+        LOG.debug(_('create_export: Volume: %(volume)s  Device ID: '
+                  '%(device_id)s')
+                  % {'volume': volumename,
+                     'device_id': device_id})
+
+        return {'provider_location': device_id}
+
+    def remove_export(self, context, volume):
+        """Driver exntry point to remove an export for a volume.
+        """
+        pass
+
+    # Mapping method for VNX
+    def _expose_paths(self, conn, configservice, vol_instance, connector):
+        """Adds a volume and initiator to a Storage Group
+        and therefore maps the volume to the host.
+        """
+        volumename = vol_instance['ElementName']
+        lun_name = vol_instance['DeviceID']
+        initiator = self._find_initiator_name(connector)
+        storage_system = vol_instance['SystemName']
+        lunmask_ctrl = self._find_lunmasking_scsi_protocol_controller(
+            storage_system, connector)
+
+        LOG.debug(_('ExposePaths: %(vol)s  ConfigServicie: %(service)s  '
+                  'LUNames: %(lun_name)s  InitiatorPortIDs: %(initiator)s  '
+                  'DeviceAccesses: 2')
+                  % {'vol': str(vol_instance.path),
+                     'service': str(configservice),
+                     'lun_name': lun_name,
+                     'initiator': initiator})
+
+        if lunmask_ctrl is None:
+            rc, controller = conn.InvokeMethod(
+                                'ExposePaths',
+                                configservice, LUNames=[lun_name],
+                                InitiatorPortIDs=[initiator],
+                                DeviceAccesses=[self._getnum(2, '16')])
+        else:
+            LOG.debug(_('ExposePaths parameter '
+                      'LunMaskingSCSIProtocolController: '
+                      '%(lunmasking)s')
+                      % {'lunmasking': str(lunmask_ctrl)})
+            rc, controller = conn.InvokeMethod(
+                                'ExposePaths',
+                                configservice, LUNames=[lun_name],
+                                DeviceAccesses=[self._getnum(2, '16')],
+                                ProtocolControllers=[lunmask_ctrl])
+
+        if rc != 0L:
+            msg = (_('Error mapping volume %s.') % volumename)
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        LOG.debug(_('ExposePaths for volume %s completed successfully.')
+                  % volumename)
+
+    # Unmapping method for VNX
+    def _hide_paths(self, conn, configservice, vol_instance, connector):
+        """Removes a volume from the Storage Group
+        and therefore unmaps the volume from the host.
+        """
+        volumename = vol_instance['ElementName']
+        device_id = vol_instance['DeviceID']
+        lunmask_ctrl = self._find_lunmasking_scsi_protocol_controller_for_vol(
+            vol_instance, connector)
+
+        LOG.debug(_('HidePaths: %(vol)s  ConfigServicie: %(service)s  '
+                  'LUNames: %(device_id)s  LunMaskingSCSIProtocolController: '
+                  '%(lunmasking)s')
+                  % {'vol': str(vol_instance.path),
+                     'service': str(configservice),
+                     'device_id': device_id,
+                     'lunmasking': str(lunmask_ctrl)})
+
+        rc, controller = conn.InvokeMethod(
+            'HidePaths', configservice,
+            LUNames=[device_id], ProtocolControllers=[lunmask_ctrl])
+
+        if rc != 0L:
+            msg = (_('Error unmapping volume %s.') % volumename)
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        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"""
+        volumename = vol_instance['ElementName']
+        masking_group = self._find_device_masking_group()
+
+        LOG.debug(_('AddMembers: ConfigServicie: %(service)s  MaskingGroup: '
+                  '%(masking_group)s  Members: %(vol)s')
+                  % {'service': str(configservice),
+                     'masking_group': str(masking_group),
+                     'vol': str(vol_instance.path)})
+
+        rc, job = conn.InvokeMethod(
+                    'AddMembers', configservice,
+                    MaskingGroup=masking_group, Members=[vol_instance.path])
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                msg = (_('Error mapping volume %(vol)s. %(error)s') %
+                       {'vol': volumename, 'error': errordesc})
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+
+        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."""
+        volumename = vol_instance['ElementName']
+        masking_group = self._find_device_masking_group()
+
+        LOG.debug(_('RemoveMembers: ConfigServicie: %(service)s  '
+                  'MaskingGroup: %(masking_group)s  Members: %(vol)s')
+                  % {'service': str(configservice),
+                     'masking_group': str(masking_group),
+                     'vol': str(vol_instance.path)})
+
+        rc, job = conn.InvokeMethod('RemoveMembers', configservice,
+                                    MaskingGroup=masking_group,
+                                    Members=[vol_instance.path])
+
+        if rc != 0L:
+            rc, errordesc = self._wait_for_job_complete(job)
+            if rc != 0L:
+                msg = (_('Error unmapping volume %(vol)s. %(error)s')
+                       % {'vol': volumename, 'error': errordesc})
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+
+        LOG.debug(_('RemoveMembers for volume %s completed successfully.')
+                  % volumename)
+
+    def check_for_export(self, context, volume_id):
+        """Make sure volume is exported."""
+        pass
+
+    def _map_lun(self, volume, connector):
+        """Maps a volume to the host."""
+        volumename = volume['name']
+        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']
+
+        configservice = self._find_controller_configuration_service(
+            storage_system)
+        if configservice is None:
+            exception_message = (_("Cannot find Controller Configuration "
+                                 "Service for storage system %s")
+                                 % storage_system)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        isVMAX = storage_system.find('SYMMETRIX')
+        if isVMAX > -1:
+            self._add_members(conn, configservice, vol_instance, connector)
+        else:
+            self._expose_paths(conn, configservice, vol_instance, connector)
+
+    def _unmap_lun(self, volume, connector):
+        """Unmaps a volume from the host."""
+        volumename = volume['name']
+        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)
+        if device_number is None:
+            LOG.info(_("Volume %s is not mapped. No volume to unmap.")
+                     % (volumename))
+            return
+
+        vol_instance = self._find_lun(volume)
+        storage_system = vol_instance['SystemName']
+
+        configservice = self._find_controller_configuration_service(
+            storage_system)
+        if configservice is None:
+            exception_message = (_("Cannot find Controller Configuration "
+                                 "Service for storage system %s")
+                                 % storage_system)
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        isVMAX = storage_system.find('SYMMETRIX')
+        if isVMAX > -1:
+            self._remove_members(conn, configservice, vol_instance, connector)
+        else:
+            self._hide_paths(conn, configservice, vol_instance, connector)
+
+    def initialize_connection(self, volume, connector):
+        """Initializes the connection and returns connection info.
+
+        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::
+
+            {
+                'driver_volume_type': 'iscsi'
+                'data': {
+                    'target_discovered': True,
+                    'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
+                    'target_portal': '127.0.0.0.1:3260',
+                    'volume_id': 1,
+                }
+            }
+
+        """
+        volumename = volume['name']
+        LOG.info(_('Initialize connection: %(volume)s')
+                 % {'volume': volumename})
+        device_number = self._find_device_number(volume)
+        if device_number is not None:
+            LOG.info(_("Volume %s is already mapped.")
+                     % (volumename))
+        else:
+            self._map_lun(volume, connector)
+
+        iscsi_properties = self._get_iscsi_properties(volume)
+        return {
+            'driver_volume_type': 'iscsi',
+            'data': iscsi_properties
+        }
+
+    def _do_iscsi_discovery(self, volume):
+
+        LOG.warn(_("ISCSI provider_location not stored, using discovery"))
+
+        (out, _err) = self._execute('iscsiadm', '-m', 'discovery',
+                                    '-t', 'sendtargets', '-p',
+                                    FLAGS.iscsi_ip_address,
+                                    run_as_root=True)
+        for target in out.splitlines():
+            return target
+        return None
+
+    def _get_iscsi_properties(self, volume):
+        """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
+        The properties are:
+
+        :target_discovered:    boolean indicating whether discovery was used
+
+        :target_iqn:    the IQN of the iSCSI target
+
+        :target_portal:    the portal of the iSCSI target
+
+        :target_lun:    the lun of the iSCSI target
+
+        :volume_id:    the id of the volume (currently used by xen)
+
+        :auth_method:, :auth_username:, :auth_password:
+
+            the authentication details. Right now, either auth_method is not
+            present meaning no authentication, or auth_method == `CHAP`
+            meaning use CHAP with the specified credentials.
+        """
+        properties = {}
+
+        location = self._do_iscsi_discovery(volume)
+        if not location:
+            raise exception.InvalidVolume(_("Could not find iSCSI export "
+                                          " for volume %s") %
+                                          (volume['name']))
+
+        LOG.debug(_("ISCSI Discovery: Found %s") % (location))
+        properties['target_discovered'] = True
+
+        results = location.split(" ")
+        properties['target_portal'] = results[0].split(",")[0]
+        properties['target_iqn'] = results[1]
+
+        device_number = self._find_device_number(volume)
+        if device_number is None:
+            exception_message = (_("Cannot find device number for volume %s")
+                                 % volume['name'])
+            raise exception.VolumeBackendAPIException(data=exception_message)
+
+        properties['target_lun'] = device_number
+
+        properties['volume_id'] = volume['id']
+
+        auth = volume['provider_auth']
+        if auth:
+            (auth_method, auth_username, auth_secret) = auth.split()
+
+            properties['auth_method'] = auth_method
+            properties['auth_username'] = auth_username
+            properties['auth_password'] = auth_secret
+
+        LOG.debug(_("ISCSI properties: %s") % (properties))
+
+        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):
+        """Disallow connection from connector"""
+        volumename = volume['name']
+        LOG.info(_('Terminate connection: %(volume)s')
+                 % {'volume': volumename})
+        self._unmap_lun(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.NovaException(_("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_id):
+        """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_id, {}, volume_file)
+
+        self.terminate_connection(volume, connector)
+
+    def _get_storage_type(self, filename=None):
+        """Get the storage type from the config file
+        """
+        if filename == None:
+            filename = FLAGS.cinder_emc_config_file
+
+        file = open(filename, 'r')
+        data = file.read()
+        file.close()
+        dom = parseString(data)
+        storageTypes = dom.getElementsByTagName('StorageType')
+        if storageTypes is not None and len(storageTypes) > 0:
+            storageType = storageTypes[0].toxml()
+            storageType = storageType.replace('<StorageType>', '')
+            storageType = storageType.replace('</StorageType>', '')
+            LOG.debug(_("Found Storage Type: %s") % (storageType))
+            return storageType
+        else:
+            LOG.debug(_("Storage Type not found."))
+            return None
+
+    def _get_masking_view(self, filename=None):
+        if filename == None:
+            filename = FLAGS.cinder_emc_config_file
+
+        file = open(filename, 'r')
+        data = file.read()
+        file.close()
+        dom = parseString(data)
+        views = dom.getElementsByTagName('MaskingView')
+        if views is not None and len(views) > 0:
+            view = views[0].toxml().replace('<MaskingView>', '')
+            view = view.replace('</MaskingView>', '')
+            LOG.debug(_("Found Masking View: %s") % (view))
+            return view
+        else:
+            LOG.debug(_("Masking View not found."))
+            return None
+
+    def _get_ecom_cred(self, filename=None):
+        if filename == None:
+            filename = FLAGS.cinder_emc_config_file
+
+        file = open(filename, 'r')
+        data = file.read()
+        file.close()
+        dom = parseString(data)
+        ecomUsers = dom.getElementsByTagName('EcomUserName')
+        if ecomUsers is not None and len(ecomUsers) > 0:
+            ecomUser = ecomUsers[0].toxml().replace('<EcomUserName>', '')
+            ecomUser = ecomUser.replace('</EcomUserName>', '')
+        ecomPasswds = dom.getElementsByTagName('EcomPassword')
+        if ecomPasswds is not None and len(ecomPasswds) > 0:
+            ecomPasswd = ecomPasswds[0].toxml().replace('<EcomPassword>', '')
+            ecomPasswd = ecomPasswd.replace('</EcomPassword>', '')
+        if ecomUser is not None and ecomPasswd is not None:
+            return ecomUser, ecomPasswd
+        else:
+            LOG.debug(_("Ecom user not found."))
+            return None
+
+    def _get_ecom_server(self, filename=None):
+        if filename == None:
+            filename = FLAGS.cinder_emc_config_file
+
+        file = open(filename, 'r')
+        data = file.read()
+        file.close()
+        dom = parseString(data)
+        ecomIps = dom.getElementsByTagName('EcomServerIp')
+        if ecomIps is not None and len(ecomIps) > 0:
+            ecomIp = ecomIps[0].toxml().replace('<EcomServerIp>', '')
+            ecomIp = ecomIp.replace('</EcomServerIp>', '')
+        ecomPorts = dom.getElementsByTagName('EcomServerPort')
+        if ecomPorts is not None and len(ecomPorts) > 0:
+            ecomPort = ecomPorts[0].toxml().replace('<EcomServerPort>', '')
+            ecomPort = ecomPort.replace('</EcomServerPort>', '')
+        if ecomIp is not None and ecomPort is not None:
+            LOG.debug(_("Ecom IP: %(ecomIp)s Port: %(ecomPort)s") % (locals()))
+            return ecomIp, ecomPort
+        else:
+            LOG.debug(_("Ecom server not found."))
+            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),
+                                     default_namespace='root/emc')
+
+        return conn
+
+    def _find_replication_service(self, storage_system):
+        foundRepService = None
+        conn = self._get_ecom_connection()
+        repservices = conn.EnumerateInstanceNames('EMC_ReplicationService')
+        for repservice in repservices:
+            if storage_system == repservice['SystemName']:
+                foundRepService = repservice
+                LOG.debug(_("Found Replication Service: %s")
+                          % (str(repservice)))
+                break
+
+        return foundRepService
+
+    def _find_storage_configuration_service(self, storage_system):
+        foundConfigService = None
+        conn = self._get_ecom_connection()
+        configservices = conn.EnumerateInstanceNames(
+            'EMC_StorageConfigurationService')
+        for configservice in configservices:
+            if storage_system == configservice['SystemName']:
+                foundConfigService = configservice
+                LOG.debug(_("Found Storage Configuration Service: %s")
+                          % (str(configservice)))
+                break
+
+        return foundConfigService
+
+    def _find_controller_configuration_service(self, storage_system):
+        foundConfigService = None
+        conn = self._get_ecom_connection()
+        configservices = conn.EnumerateInstanceNames(
+            'EMC_ControllerConfigurationService')
+        for configservice in configservices:
+            if storage_system == configservice['SystemName']:
+                foundConfigService = configservice
+                LOG.debug(_("Found Controller Configuration Service: %s")
+                          % (str(configservice)))
+                break
+
+        return foundConfigService
+
+     # Find pool based on storage_type
+    def _find_pool(self, storage_type):
+        foundPool = None
+        systemname = None
+        conn = self._get_ecom_connection()
+        vpools = conn.EnumerateInstanceNames('EMC_VirtualProvisioningPool')
+        upools = conn.EnumerateInstanceNames('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:
+                    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
+
+        LOG.debug(_("Pool: %(pool)s  SystemName: %(systemname)s.")
+                  % {'pool': str(foundPool), 'systemname': systemname})
+        return foundPool, systemname
+
+    def _parse_pool_instance_id(self, instanceid):
+        # Example of pool InstanceId: CLARiiON+APM00115204878+U+Pool 0
+        poolname = None
+        systemname = None
+        endp = instanceid.rfind('+')
+        if endp > -1:
+            poolname = instanceid[endp + 1:]
+
+        idarray = instanceid.split('+')
+        if len > 2:
+            systemname = idarray[0] + '+' + idarray[1]
+
+        LOG.debug(_("Pool name: %(poolname)s  System name: %(systemname)s.")
+                  % {'poolname': poolname, 'systemname': systemname})
+        return poolname, systemname
+
+    def _find_lun(self, volume):
+        foundinstance = None
+        try:
+            device_id = volume['provider_location']
+        except Exception:
+            device_id = None
+
+        volumename = volume['name']
+        conn = self._get_ecom_connection()
+
+        names = conn.EnumerateInstanceNames('EMC_StorageVolume')
+
+        for n in names:
+            if device_id is not None:
+                if n['DeviceID'] == device_id:
+                    vol_instance = conn.GetInstance(n)
+                    foundinstance = vol_instance
+                    break
+                else:
+                    continue
+
+            else:
+                vol_instance = conn.GetInstance(n)
+                if vol_instance['ElementName'] == volumename:
+                    foundinstance = vol_instance
+                    volume['provider_location'] = foundinstance['DeviceID']
+                    break
+
+        if foundinstance is None:
+            LOG.debug(_("Volume %(volumename)s not found on the array.")
+                      % {'volumename': volumename})
+        else:
+            LOG.debug(_("Volume name: %(volumename)s  Volume instance: "
+                      "%(vol_instance)s.")
+                      % {'volumename': volumename,
+                         'vol_instance': str(foundinstance.path)})
+
+        return foundinstance
+
+    def _find_storage_sync_sv_sv(self, snapshotname, volumename):
+        foundsyncname = None
+        storage_system = None
+
+        LOG.debug(_("Source: %(volumename)s  Target: %(snapshotname)s.")
+                  % {'volumename': volumename, 'snapshotname': snapshotname})
+
+        conn = self._get_ecom_connection()
+
+        names = conn.EnumerateInstanceNames('SE_StorageSynchronized_SV_SV')
+
+        for n in names:
+            snapshot_instance = conn.GetInstance(n['SyncedElement'],
+                                                 LocalOnly=False)
+            if snapshotname != snapshot_instance['ElementName']:
+                continue
+
+            vol_instance = conn.GetInstance(n['SystemElement'],
+                                            LocalOnly=False)
+            if vol_instance['ElementName'] == volumename:
+                foundsyncname = n
+                storage_system = vol_instance['SystemName']
+                break
+
+        if foundsyncname is None:
+            LOG.debug(_("Source: %(volumename)s  Target: %(snapshotname)s. "
+                      "Storage Synchronized not found. ")
+                      % {'volumename': volumename,
+                         'snapshotname': snapshotname})
+        else:
+            LOG.debug(_("Storage system: %(storage_system)s  "
+                      "Storage Synchronized instance: %(sync)s.")
+                      % {'storage_system': storage_system,
+                         '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']
+
+        LOG.debug(_("Initiator name: %(initiator)s.")
+                  % {'initiator': foundinitiatorname})
+        return foundinitiatorname
+
+    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)
+            jobstate = jobinstance['JobState']
+            # From ValueMap of JobState in CIM_ConcreteJob
+            # 2L=New, 3L=Starting, 4L=Running, 32767L=Queue Pending
+            # ValueMap("2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13..32767,
+            # 32768..65535"),
+            # Values("New, Starting, Running, Suspended, Shutting Down,
+            # Completed, Terminated, Killed, Exception, Service,
+            # Query Pending, DMTF Reserved, Vendor Reserved")]
+            if jobstate in [2L, 3L, 4L, 32767L]:
+                time.sleep(10)
+            else:
+                break
+
+        rc = jobinstance['ErrorCode']
+        errordesc = jobinstance['ErrorDescription']
+
+        return rc, errordesc
+
+    # Find LunMaskingSCSIProtocolController for the local host on the
+    # specified storage system
+    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(
+            'EMC_LunMaskingSCSIProtocolController')
+        for ctrl in controllers:
+            if storage_system != ctrl['SystemName']:
+                continue
+            associators = 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
+                    break
+
+            if foundCtrl is not None:
+                break
+
+        LOG.debug(_("LunMaskingSCSIProtocolController for storage system "
+                  "%(storage_system)s and initiator %(initiator)s is  "
+                  "%(ctrl)s.")
+                  % {'storage_system': storage_system,
+                     'initiator': initiator,
+                     'ctrl': str(foundCtrl)})
+        return foundCtrl
+
+    # Find LunMaskingSCSIProtocolController for the local host and the
+    # specified storage volume
+    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(
+                        vol_instance.path,
+                        resultClass='EMC_LunMaskingSCSIProtocolController')
+
+        for ctrl in controllers:
+            associators = 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
+                    break
+
+            if foundCtrl is not None:
+                break
+
+        LOG.debug(_("LunMaskingSCSIProtocolController for storage volume "
+                  "%(vol)s and initiator %(initiator)s is  %(ctrl)s.")
+                  % {'vol': str(vol_instance.path), 'initiator': initiator,
+                     'ctrl': str(foundCtrl)})
+        return foundCtrl
+
+    # Find an available device number that a host can see
+    def _find_avail_device_number(self, storage_system):
+        out_device_number = '000000'
+        out_num_device_number = 0
+        numlist = []
+        myunitnames = []
+
+        conn = self._get_ecom_connection()
+        unitnames = conn.EnumerateInstanceNames(
+            'CIM_ProtocolControllerForUnit')
+        for unitname in unitnames:
+            controller = unitname['Antecedent']
+            if storage_system != controller['SystemName']:
+                continue
+            classname = controller['CreationClassName']
+            index = classname.find('LunMaskingSCSIProtocolController')
+            if index > -1:
+                unitinstance = conn.GetInstance(unitname, LocalOnly=False)
+                numDeviceNumber = int(unitinstance['DeviceNumber'])
+                numlist.append(numDeviceNumber)
+                myunitnames.append(unitname)
+
+        maxnum = max(numlist)
+        out_num_device_number = maxnum + 1
+
+        out_device_number = '%06d' % out_num_device_number
+
+        LOG.debug(_("Available device number on %(storage)s: %(device)s.")
+                  % {'storage': storage_system, 'device': out_device_number})
+        return out_device_number
+
+    # Find a device number that a host can see for a volume
+    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)
+
+        unitnames = conn.ReferenceNames(
+                        vol_instance.path,
+                        ResultClass='CIM_ProtocolControllerForUnit')
+
+        for unitname in unitnames:
+            controller = unitname['Antecedent']
+            classname = controller['CreationClassName']
+            index = classname.find('LunMaskingSCSIProtocolController')
+            if index > -1:  # VNX
+                # Get an instance of CIM_ProtocolControllerForUnit
+                unitinstance = 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)
+                    numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
+                    out_num_device_number = numDeviceNumber
+                    break
+
+        if out_num_device_number is None:
+            LOG.info(_("Device number not found for volume "
+                     "%(volumename)s %(vol_instance)s.") %
+                     {'volumename': volumename,
+                      'vol_instance': str(vol_instance.path)})
+        else:
+            LOG.debug(_("Found device number %(device)d for volume "
+                      "%(volumename)s %(vol_instance)s.") %
+                      {'device': out_num_device_number,
+                       'volumename': volumename,
+                       'vol_instance': str(vol_instance.path)})
+
+        return out_num_device_number
+
+    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(
+            'EMC_LunMaskingSCSIProtocolController')
+        for view in maskingviews:
+            instance = conn.GetInstance(view, LocalOnly=False)
+            if maskingview_name == instance['ElementName']:
+                foundView = view
+                break
+
+        groups = conn.AssociatorNames(foundView,
+                                      ResultClass='SE_DeviceMaskingGroup')
+        foundMaskingGroup = groups[0]
+
+        LOG.debug(_("Masking view: %(view)s DeviceMaskingGroup: %(masking)s.")
+                  % {'view': maskingview_name,
+                     'masking': str(foundMaskingGroup)})
+
+        return foundMaskingGroup
+
+    def _getnum(self, num, datatype):
+        try:
+            result = {
+                '8': pywbem.Uint8(num),
+                '16': pywbem.Uint16(num),
+                '32': pywbem.Uint32(num),
+                '64': pywbem.Uint64(num)
+            }
+            result = result.get(datatype, num)
+        except NameError:
+            result = num
+
+        return result
diff --git a/etc/cinder/cinder_emc_config.xml.sample b/etc/cinder/cinder_emc_config.xml.sample
new file mode 100644 (file)
index 0000000..d67ff37
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<EMC>
+<!--StorageType is a thin pool name-->
+<StorageType>gold</StorageType>
+<!--MaskingView is needed only for VMAX/VMAXe-->
+<MaskingView>openstack</MaskingView>
+<!--Credentials of ECOM packaged with SMI-S-->
+<EcomServerIp>x.x.x.x</EcomServerIp>
+<EcomServerPort>xxxx</EcomServerPort>
+<EcomUserName>xxxxxxxx</EcomUserName>
+<EcomPassword>xxxxxxxx</EcomPassword>
+</EMC>