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