--- /dev/null
+# 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)
--- /dev/null
+# 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