From 5a7808d676a844c5c1327bd59ec184e26e0546b1 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Tue, 11 Feb 2014 00:00:53 -0500 Subject: [PATCH] Add EMC SMI-S FC Driver in Cinder Add an EMC SMI-S FC Driver in Cinder. It supports all required driver features. Also made enhancements to the existing SMI-S iSCSI Driver (performance enhancement, multiple pool support and thick/thin provisioning, extend volume). emc_smis_common.py is the common code used by both iSCSI driver (emc_smis_iscsi.py) and FC driver (emc_smis_fc.py). Implements blueprint emc-fibre-channel-volume-driver Change-Id: Idace398ef60863811911511bcbbfcecb3bf58270 --- cinder/tests/test_emc.py | 1060 +++++++++++++----- cinder/volume/drivers/emc/emc_smis_common.py | 321 ++++-- cinder/volume/drivers/emc/emc_smis_fc.py | 198 ++++ cinder/volume/drivers/emc/emc_smis_iscsi.py | 66 +- 4 files changed, 1297 insertions(+), 348 deletions(-) create mode 100644 cinder/volume/drivers/emc/emc_smis_fc.py diff --git a/cinder/tests/test_emc.py b/cinder/tests/test_emc.py index d7bd28017..1a13ba01c 100644 --- a/cinder/tests/test_emc.py +++ b/cinder/tests/test_emc.py @@ -1,6 +1,4 @@ - -# Copyright (c) 2012 EMC Corporation, Inc. -# Copyright (c) 2012 OpenStack Foundation +# Copyright (c) 2012 - 2014 EMC Corporation, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,132 +13,26 @@ # License for the specific language governing permissions and limitations # under the License. + import os import shutil import tempfile from xml.dom.minidom import Document -import mox +import mock from cinder import exception from cinder.openstack.common import log as logging from cinder import test -from cinder.volume import configuration as conf +from cinder import units from cinder.volume.drivers.emc.emc_smis_common import EMCSMISCommon +from cinder.volume.drivers.emc.emc_smis_fc import EMCSMISFCDriver from cinder.volume.drivers.emc.emc_smis_iscsi import EMCSMISISCSIDriver - +from cinder.volume import volume_types CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml' 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' -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_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', - '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} -test_clone3 = {'name': 'clone3', - 'size': 1, - 'volume_name': 'vol1', - 'id': '3', - 'provider_auth': None, - 'project_id': 'project', - '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 @@ -150,26 +42,199 @@ class SE_ConcreteJob(dict): pass +class SE_StorageHardwareID(dict): + pass + + +class FakeCIMInstanceName(dict): + + def fake_getinstancename(self, classname, bindings): + instancename = FakeCIMInstanceName() + for key in bindings: + instancename[key] = bindings[key] + instancename.classname = classname + instancename.namespace = 'root/emc' + return instancename + + +class FakeDB(): + def volume_update(self, context, volume_id, model_update): + pass + + def snapshot_update(self, context, snapshot_id, model_update): + pass + + def volume_get(self, context, volume_id): + conn = FakeEcomConnection() + objectpath = {} + objectpath['CreationClassName'] = 'Clar_StorageVolume' + if volume_id == 'vol1': + device_id = '1' + objectpath['DeviceID'] = device_id + else: + objectpath['DeviceID'] = volume_id + return conn.GetInstance(objectpath) + + +class EMCSMISCommonData(): + connector = {'ip': '10.0.0.2', + 'initiator': 'iqn.1993-08.org.debian:01:222', + 'wwpns': ["123456789012345", "123456789054321"], + 'wwnns': ["223456789012345", "223456789054321"], + 'host': 'fakehost'} + + 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' + 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_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', + 'volume_name': 'vol-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} + test_clone3 = {'name': 'clone3', + 'size': 1, + 'volume_name': 'vol1', + 'id': '3', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'clone3', + 'display_description': 'cloned volume', + 'volume_type_id': None} + test_snapshot_vmax = {'name': 'snapshot_vmax', + 'size': 1, + 'id': '4445', + 'volume_name': 'vol-vol1', + 'volume_size': 1, + 'project_id': 'project'} + failed_snapshot_replica = {'name': 'failed_snapshot_replica', + 'size': 1, + 'volume_name': 'vol-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': 'vol-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} + failed_extend_vol = {'name': 'failed_extend_vol', + 'size': 1, + 'volume_name': 'failed_extend_vol', + 'id': '9', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'failed_extend_vol', + 'display_description': 'test failed extend volume', + 'volume_type_id': None} + + class FakeEcomConnection(): + def __init__(self, *args, **kwargs): + self.data = EMCSMISCommonData() + def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None, ElementType=None, Size=None, SyncType=None, SourceElement=None, Operation=None, Synchronization=None, - TheElements=None, + TheElements=None, TheElement=None, LUNames=None, InitiatorPortIDs=None, DeviceAccesses=None, ProtocolControllers=None, - MaskingGroup=None, Members=None): + MaskingGroup=None, Members=None, + HardwareId=None): rc = 0L myjob = SE_ConcreteJob() myjob.classname = 'SE_ConcreteJob' myjob['InstanceID'] = '9999' myjob['status'] = 'success' + myjob['type'] = ElementName if ElementName == 'failed_vol' and \ MethodName == 'CreateOrModifyElementFromStoragePool': rc = 10L myjob['status'] = 'failure' + elif TheElement and TheElement['ElementName'] == 'failed_extend_vol' \ + and MethodName == 'CreateOrModifyElementFromStoragePool': + rc = 10L + myjob['status'] = 'failure' + elif MethodName == 'CreateOrModifyElementFromStoragePool': + rc = 0L + myjob['status'] = 'success' elif ElementName == 'failed_snapshot_replica' and \ MethodName == 'CreateElementReplica': rc = 10L @@ -195,6 +260,18 @@ class FakeEcomConnection(): MethodName == 'EMCReturnToStoragePool': rc = 10L myjob['status'] = 'failure' + elif HardwareId: + rc = 0L + targetendpoints = {} + endpoints = [] + endpoint = {} + endpoint['Name'] = '1234567890123' + endpoints.append(endpoint) + endpoint2 = {} + endpoint2['Name'] = '0987654321321' + endpoints.append(endpoint2) + targetendpoints['TargetEndpoints'] = endpoints + return rc, targetendpoints job = {'Job': myjob} return rc, job @@ -223,6 +300,8 @@ class FakeEcomConnection(): result = self._enum_lunmaskctrls() elif name == 'EMC_StorageProcessorSystem': result = self._enum_processors() + elif name == 'EMC_StorageHardwareIDManagementService': + result = self._enum_hdwidmgmts() else: result = self._default_enum() return result @@ -233,6 +312,8 @@ class FakeEcomConnection(): result = self._enum_pool_details() elif name == 'EMC_UnifiedStoragePool': result = self._enum_pool_details() + elif name == 'SE_StorageHardwareID': + result = self._enum_storhdwids() else: result = self._default_enum() return result @@ -243,7 +324,7 @@ class FakeEcomConnection(): except KeyError: name = objectpath.classname result = None - if name == 'Clar_StorageVolume': + if name == 'Clar_StorageVolume' or name == 'Symm_StorageVolume': result = self._getinstance_storagevolume(objectpath) elif name == 'CIM_ProtocolControllerForUnit': result = self._getinstance_unit(objectpath) @@ -263,6 +344,9 @@ class FakeEcomConnection(): result = self._assoc_hdwid() elif resultClass == 'EMC_iSCSIProtocolEndpoint': result = self._assoc_endpoint() + # Added test for EMC_StorageVolume + elif resultClass == 'EMC_StorageVolume': + result = self._assoc_storagevolume(objectpath) else: result = self._default_assoc(objectpath) return result @@ -286,26 +370,26 @@ class FakeEcomConnection(): return result def _ref_unitnames(self): - units = [] - unit = {} + unitnames = [] + unitname = {} dependent = {} - dependent['CreationClassName'] = vol_creationclass - dependent['DeviceID'] = test_volume['id'] - dependent['ElementName'] = test_volume['name'] - dependent['SystemName'] = storage_system + dependent['CreationClassName'] = self.data.vol_creationclass + dependent['DeviceID'] = self.data.test_volume['id'] + dependent['ElementName'] = self.data.test_volume['name'] + dependent['SystemName'] = self.data.storage_system antecedent = {} - antecedent['CreationClassName'] = lunmask_creationclass - antecedent['DeviceID'] = lunmaskctrl_id - antecedent['SystemName'] = storage_system + antecedent['CreationClassName'] = self.data.lunmask_creationclass + antecedent['DeviceID'] = self.data.lunmaskctrl_id + antecedent['SystemName'] = self.data.storage_system - unit['Dependent'] = dependent - unit['Antecedent'] = antecedent - unit['CreationClassName'] = unit_creationclass - units.append(unit) + unitname['Dependent'] = dependent + unitname['Antecedent'] = antecedent + unitname['CreationClassName'] = self.data.unit_creationclass + unitnames.append(unitname) - return units + return unitnames def _default_ref(self, objectpath): return objectpath @@ -313,7 +397,7 @@ class FakeEcomConnection(): def _assoc_hdwid(self): assocs = [] assoc = {} - assoc['StorageID'] = initiator1 + assoc['StorageID'] = self.data.initiator1 assocs.append(assoc) return assocs @@ -321,7 +405,42 @@ class FakeEcomConnection(): assocs = [] assoc = {} assoc['Name'] = 'iqn.1992-04.com.emc:cx.apm00123907237.a8,t,0x0001' - assoc['SystemName'] = storage_system + '+SP_A+8' + assoc['SystemName'] = self.data.storage_system + '+SP_A+8' + assocs.append(assoc) + return assocs + + # Added test for EMC_StorageVolume associators + def _assoc_storagevolume(self, objectpath): + assocs = [] + if objectpath['type'] == 'failed_delete_vol': + vol = self.data.failed_delete_vol + elif objectpath['type'] == 'vol1': + vol = self.data.test_volume + elif objectpath['type'] == 'failed_vol': + vol = self.data.test_failed_volume + elif objectpath['type'] == 'failed_clone_sync': + vol = self.data.failed_clone_sync + elif objectpath['type'] == 'failed_clone_replica': + vol = self.data.failed_clone_replica + elif objectpath['type'] == 'failed_snapshot_replica': + vol = self.data.failed_snapshot_replica + elif objectpath['type'] == 'failed_snapshot_sync': + vol = self.data.failed_snapshot_sync + elif objectpath['type'] == 'clone1': + vol = self.data.test_clone + elif objectpath['type'] == 'clone3': + vol = self.data.test_clone3 + elif objectpath['type'] == 'snapshot1': + vol = self.data.test_snapshot + elif objectpath['type'] == 'snapshot_vmax': + vol = self.data.test_snapshot_vmax + elif objectpath['type'] == 'failed_extend_vol': + vol = self.data.failed_extend_vol + else: + return None + + vol['DeviceID'] = vol['id'] + assoc = self._getinstance_storagevolume(vol) assocs.append(assoc) return assocs @@ -335,13 +454,18 @@ class FakeEcomConnection(): return objectpath def _getinstance_storagevolume(self, objectpath): + foundinstance = None instance = EMC_StorageVolume() vols = self._enum_storagevolumes() for vol in vols: if vol['DeviceID'] == objectpath['DeviceID']: instance = vol break - return instance + if not instance: + foundinstance = None + else: + foundinstance = instance + return foundinstance def _getinstance_syncsvsv(self, objectpath): foundsync = None @@ -355,28 +479,28 @@ class FakeEcomConnection(): def _getinstance_lunmask(self): lunmask = {} - lunmask['CreationClassName'] = lunmask_creationclass - lunmask['DeviceID'] = lunmaskctrl_id - lunmask['SystemName'] = storage_system + lunmask['CreationClassName'] = self.data.lunmask_creationclass + lunmask['DeviceID'] = self.data.lunmaskctrl_id + lunmask['SystemName'] = self.data.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 + dependent['CreationClassName'] = self.data.vol_creationclass + dependent['DeviceID'] = self.data.test_volume['id'] + dependent['ElementName'] = self.data.test_volume['name'] + dependent['SystemName'] = self.data.storage_system antecedent = {} - antecedent['CreationClassName'] = lunmask_creationclass - antecedent['DeviceID'] = lunmaskctrl_id - antecedent['SystemName'] = storage_system + antecedent['CreationClassName'] = self.data.lunmask_creationclass + antecedent['DeviceID'] = self.data.lunmaskctrl_id + antecedent['SystemName'] = self.data.storage_system unit['Dependent'] = dependent unit['Antecedent'] = antecedent - unit['CreationClassName'] = unit_creationclass + unit['CreationClassName'] = self.data.unit_creationclass unit['DeviceNumber'] = '0' return unit @@ -400,31 +524,34 @@ class FakeEcomConnection(): def _enum_replicationservices(self): rep_services = [] rep_service = {} - rep_service['SystemName'] = storage_system - rep_service['CreationClassName'] = rep_service_creationclass + rep_service['SystemName'] = self.data.storage_system + rep_service['CreationClassName'] = self.data.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_service['SystemName'] = self.data.storage_system + conf_service['CreationClassName'] =\ + self.data.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_service['SystemName'] = self.data.storage_system + conf_service['CreationClassName'] =\ + self.data.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['InstanceID'] = self.data.storage_system + '+U+' +\ + self.data.storage_type pool['CreationClassName'] = 'Clar_UnifiedStoragePool' pools.append(pool) return pools @@ -432,7 +559,8 @@ class FakeEcomConnection(): def _enum_pool_details(self): pools = [] pool = {} - pool['InstanceID'] = storage_system + '+U+' + storage_type + pool['InstanceID'] = self.data.storage_system + '+U+' +\ + self.data.storage_type pool['CreationClassName'] = 'Clar_UnifiedStoragePool' pool['TotalManagedSpace'] = 12345678 pool['RemainingManagedSpace'] = 123456 @@ -441,90 +569,239 @@ class FakeEcomConnection(): def _enum_storagevolumes(self): vols = [] + vol = EMC_StorageVolume() + vol['name'] = self.data.test_volume['name'] vol['CreationClassName'] = 'Clar_StorageVolume' - vol['ElementName'] = test_volume['name'] - vol['DeviceID'] = test_volume['id'] - vol['SystemName'] = storage_system - vol.path = {'DeviceID': vol['DeviceID']} + vol['ElementName'] = self.data.test_volume['name'] + vol['DeviceID'] = self.data.test_volume['id'] + vol['SystemName'] = self.data.storage_system + # Added vol to vol.path + vol['SystemCreationClassName'] = 'Clar_StorageSystem' + vol.path = vol + vol.path.classname = vol['CreationClassName'] + + name = {} + name['classname'] = 'Clar_StorageVolume' + keys = {} + keys['CreationClassName'] = 'Clar_StorageVolume' + keys['SystemName'] = self.data.storage_system + keys['DeviceID'] = vol['DeviceID'] + keys['SystemCreationClassName'] = 'Clar_StorageSystem' + name['keybindings'] = keys + vol['provider_location'] = str(name) + vols.append(vol) snap_vol = EMC_StorageVolume() + snap_vol['name'] = self.data.test_snapshot['name'] 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']} + snap_vol['ElementName'] = self.data.test_snapshot['name'] + snap_vol['DeviceID'] = self.data.test_snapshot['id'] + snap_vol['SystemName'] = self.data.storage_system + # Added vol to path + snap_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + snap_vol.path = snap_vol + snap_vol.path.classname = snap_vol['CreationClassName'] + + name2 = {} + name2['classname'] = 'Clar_StorageVolume' + keys2 = {} + keys2['CreationClassName'] = 'Clar_StorageVolume' + keys2['SystemName'] = self.data.storage_system + keys2['DeviceID'] = snap_vol['DeviceID'] + keys2['SystemCreationClassName'] = 'Clar_StorageSystem' + name2['keybindings'] = keys2 + snap_vol['provider_location'] = str(name2) + vols.append(snap_vol) clone_vol = EMC_StorageVolume() + clone_vol['name'] = self.data.test_clone['name'] 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']} + clone_vol['ElementName'] = self.data.test_clone['name'] + clone_vol['DeviceID'] = self.data.test_clone['id'] + clone_vol['SystemName'] = self.data.storage_system + # Added vol to vol.path + clone_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + clone_vol.path = clone_vol + clone_vol.path.classname = clone_vol['CreationClassName'] vols.append(clone_vol) clone_vol3 = EMC_StorageVolume() + clone_vol3['name'] = self.data.test_clone3['name'] clone_vol3['CreationClassName'] = 'Clar_StorageVolume' - clone_vol3['ElementName'] = test_clone3['name'] - clone_vol3['DeviceID'] = test_clone3['id'] - clone_vol3['SystemName'] = storage_system - clone_vol3.path = {'DeviceID': clone_vol3['DeviceID']} + clone_vol3['ElementName'] = self.data.test_clone3['name'] + clone_vol3['DeviceID'] = self.data.test_clone3['id'] + clone_vol3['SystemName'] = self.data.storage_system + # Added vol to vol.path + clone_vol3['SystemCreationClassName'] = 'Clar_StorageSystem' + clone_vol3.path = clone_vol3 + clone_vol3.path.classname = clone_vol3['CreationClassName'] vols.append(clone_vol3) snap_vol_vmax = EMC_StorageVolume() + snap_vol_vmax['name'] = self.data.test_snapshot_vmax['name'] 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']} + snap_vol_vmax['ElementName'] = self.data.test_snapshot_vmax['name'] + snap_vol_vmax['DeviceID'] = self.data.test_snapshot_vmax['id'] + snap_vol_vmax['SystemName'] = self.data.storage_system_vmax + # Added vol to vol.path + snap_vol_vmax['SystemCreationClassName'] = 'Symm_StorageSystem' + snap_vol_vmax.path = snap_vol_vmax + snap_vol_vmax.path.classname = snap_vol_vmax['CreationClassName'] + + name3 = {} + name3['classname'] = 'Clar_StorageVolume' + keys3 = {} + keys3['CreationClassName'] = 'Clar_StorageVolume' + keys3['SystemName'] = self.data.storage_system + keys3['DeviceID'] = snap_vol_vmax['DeviceID'] + keys3['SystemCreationClassName'] = 'Clar_StorageSystem' + name3['keybindings'] = keys3 + snap_vol_vmax['provider_location'] = str(name3) + vols.append(snap_vol_vmax) failed_snap_replica = EMC_StorageVolume() + failed_snap_replica['name'] = self.data.failed_snapshot_replica['name'] 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']} + failed_snap_replica['ElementName'] =\ + self.data.failed_snapshot_replica['name'] + failed_snap_replica['DeviceID'] =\ + self.data.failed_snapshot_replica['id'] + failed_snap_replica['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_snap_replica['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_snap_replica.path = failed_snap_replica + failed_snap_replica.path.classname =\ + failed_snap_replica['CreationClassName'] + + name4 = {} + name4['classname'] = 'Clar_StorageVolume' + keys4 = {} + keys4['CreationClassName'] = 'Clar_StorageVolume' + keys4['SystemName'] = self.data.storage_system + keys4['DeviceID'] = failed_snap_replica['DeviceID'] + keys4['SystemCreationClassName'] = 'Clar_StorageSystem' + name4['keybindings'] = keys4 + failed_snap_replica['provider_location'] = str(name4) + vols.append(failed_snap_replica) failed_snap_sync = EMC_StorageVolume() + failed_snap_sync['name'] = self.data.failed_snapshot_sync['name'] 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']} + failed_snap_sync['ElementName'] =\ + self.data.failed_snapshot_sync['name'] + failed_snap_sync['DeviceID'] = self.data.failed_snapshot_sync['id'] + failed_snap_sync['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_snap_sync['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_snap_sync.path = failed_snap_sync + failed_snap_sync.path.classname =\ + failed_snap_sync['CreationClassName'] + + name5 = {} + name5['classname'] = 'Clar_StorageVolume' + keys5 = {} + keys5['CreationClassName'] = 'Clar_StorageVolume' + keys5['SystemName'] = self.data.storage_system + keys5['DeviceID'] = failed_snap_sync['DeviceID'] + keys5['SystemCreationClassName'] = 'Clar_StorageSystem' + name5['keybindings'] = keys5 + failed_snap_sync['provider_location'] = str(name5) + vols.append(failed_snap_sync) failed_clone_rep = EMC_StorageVolume() + failed_clone_rep['name'] = self.data.failed_clone_replica['name'] 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']} + failed_clone_rep['ElementName'] =\ + self.data.failed_clone_replica['name'] + failed_clone_rep['DeviceID'] = self.data.failed_clone_replica['id'] + failed_clone_rep['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_clone_rep['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_clone_rep.path = failed_clone_rep + failed_clone_rep.path.classname =\ + failed_clone_rep['CreationClassName'] vols.append(failed_clone_rep) failed_clone_s = EMC_StorageVolume() + failed_clone_s['name'] = self.data.failed_clone_sync['name'] 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']} + failed_clone_s['ElementName'] = self.data.failed_clone_sync['name'] + failed_clone_s['DeviceID'] = self.data.failed_clone_sync['id'] + failed_clone_s['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_clone_s['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_clone_s.path = failed_clone_s + failed_clone_s.path.classname =\ + failed_clone_s['CreationClassName'] vols.append(failed_clone_s) failed_delete_vol = EMC_StorageVolume() + failed_delete_vol['name'] = 'failed_delete_vol' 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']} + failed_delete_vol['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_delete_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_delete_vol.path = failed_delete_vol + failed_delete_vol.path.classname =\ + failed_delete_vol['CreationClassName'] vols.append(failed_delete_vol) + failed_vol = EMC_StorageVolume() + failed_vol['name'] = 'failed__vol' + failed_vol['CreationClassName'] = 'Clar_StorageVolume' + failed_vol['ElementName'] = 'failed_vol' + failed_vol['DeviceID'] = '4' + failed_vol['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_vol.path = failed_vol + failed_vol.path.classname =\ + failed_vol['CreationClassName'] + + name_failed = {} + name_failed['classname'] = 'Clar_StorageVolume' + keys_failed = {} + keys_failed['CreationClassName'] = 'Clar_StorageVolume' + keys_failed['SystemName'] = self.data.storage_system + keys_failed['DeviceID'] = failed_vol['DeviceID'] + keys_failed['SystemCreationClassName'] = 'Clar_StorageSystem' + name_failed['keybindings'] = keys_failed + failed_vol['provider_location'] = str(name_failed) + + vols.append(failed_vol) + + failed_extend_vol = EMC_StorageVolume() + failed_extend_vol['name'] = 'failed_extend_vol' + failed_extend_vol['CreationClassName'] = 'Clar_StorageVolume' + failed_extend_vol['ElementName'] = 'failed_extend_vol' + failed_extend_vol['DeviceID'] = '9' + failed_extend_vol['SystemName'] = self.data.storage_system + # Added vol to vol.path + failed_extend_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + failed_extend_vol.path = failed_extend_vol + failed_extend_vol.path.classname =\ + failed_extend_vol['CreationClassName'] + + name_extend_failed = {} + name_extend_failed['classname'] = 'Clar_StorageVolume' + keys_extend_failed = {} + keys_extend_failed['CreationClassName'] = 'Clar_StorageVolume' + keys_extend_failed['SystemName'] = self.data.storage_system + keys_extend_failed['DeviceID'] = failed_extend_vol['DeviceID'] + keys_extend_failed['SystemCreationClassName'] = 'Clar_StorageSystem' + name_extend_failed['keybindings'] = keys_extend_failed + failed_extend_vol['provider_location'] = str(name_extend_failed) + + vols.append(failed_extend_vol) + return vols def _enum_syncsvsvs(self): @@ -573,9 +850,9 @@ class FakeEcomConnection(): def _enum_lunmaskctrls(self): ctrls = [] ctrl = {} - ctrl['CreationClassName'] = lunmask_creationclass - ctrl['DeviceID'] = lunmaskctrl_id - ctrl['SystemName'] = storage_system + ctrl['CreationClassName'] = self.data.lunmask_creationclass + ctrl['DeviceID'] = self.data.lunmaskctrl_id + ctrl['SystemName'] = self.data.storage_system ctrls.append(ctrl) return ctrls @@ -583,10 +860,26 @@ class FakeEcomConnection(): ctrls = [] ctrl = {} ctrl['CreationClassName'] = 'Clar_StorageProcessorSystem' - ctrl['Name'] = storage_system + '+SP_A' + ctrl['Name'] = self.data.storage_system + '+SP_A' ctrls.append(ctrl) return ctrls + def _enum_hdwidmgmts(self): + services = [] + srv = {} + srv['SystemName'] = self.data.storage_system + services.append(srv) + return services + + def _enum_storhdwids(self): + storhdwids = [] + hdwid = SE_StorageHardwareID() + hdwid['StorageID'] = self.data.connector['wwpns'][0] + + hdwid.path = hdwid + storhdwids.append(hdwid) + return storhdwids + def _default_enum(self): names = [] name = {} @@ -598,23 +891,30 @@ class FakeEcomConnection(): class EMCSMISISCSIDriverTestCase(test.TestCase): def setUp(self): + + self.data = EMCSMISCommonData() + self.tempdir = tempfile.mkdtemp() super(EMCSMISISCSIDriverTestCase, self).setUp() self.config_file_path = None self.create_fake_config_file() - configuration = mox.MockObject(conf.Configuration) + configuration = mock.Mock() configuration.cinder_emc_config_file = self.config_file_path - configuration.append_config_values(mox.IgnoreArg()) self.stubs.Set(EMCSMISISCSIDriver, '_do_iscsi_discovery', self.fake_do_iscsi_discovery) self.stubs.Set(EMCSMISCommon, '_get_ecom_connection', self.fake_ecom_connection) + instancename = FakeCIMInstanceName() + self.stubs.Set(EMCSMISCommon, '_getinstancename', + instancename.fake_getinstancename) driver = EMCSMISISCSIDriver(configuration=configuration) + driver.db = FakeDB() self.driver = driver def create_fake_config_file(self): + doc = Document() emc = doc.createElement("EMC") doc.appendChild(emc) @@ -644,7 +944,7 @@ class EMCSMISISCSIDriverTestCase(test.TestCase): emc.appendChild(ecompassword) ecompassword.appendChild(ecompasswordtext) - self.config_file_path = self.tempdir + '/' + config_file_name + self.config_file_path = self.tempdir + '/' + self.data.config_file_name f = open(self.config_file_path, 'w') doc.writexml(f) f.close() @@ -665,95 +965,116 @@ class EMCSMISISCSIDriverTestCase(test.TestCase): self.driver.get_volume_stats(True) def test_create_destroy(self): - self.driver.create_volume(test_volume) - self.driver.delete_volume(test_volume) + self.driver.create_volume(self.data.test_volume) + self.driver.delete_volume(self.data.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(self.data.test_volume) + self.driver.create_snapshot(self.data.test_snapshot) self.driver.create_volume_from_snapshot( - test_clone, test_snapshot) + self.data.test_clone, self.data.test_snapshot) self.driver.create_cloned_volume( - test_clone3, test_volume) - self.driver.delete_volume(test_clone) - self.driver.delete_volume(test_clone3) - self.driver.delete_snapshot(test_snapshot) - self.driver.delete_volume(test_volume) + self.data.test_clone3, self.data.test_volume) + self.driver.delete_volume(self.data.test_clone) + self.driver.delete_volume(self.data.test_clone3) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.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'] - test_volume['EMCCurrentOwningStorageProcessor'] = 'SP_A' - 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) + self.driver.create_volume(self.data.test_volume) + self.data.test_volume['EMCCurrentOwningStorageProcessor'] = 'SP_A' + connection_info = self.driver.initialize_connection( + self.data.test_volume, + self.data.connector) + self.driver.terminate_connection(self.data.test_volume, + self.data.connector) + self.driver.delete_volume(self.data.test_volume) def test_create_volume_failed(self): self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, - test_failed_volume) + self.data.test_failed_volume) def test_create_volume_snapshot_unsupported(self): - self.driver.create_volume(test_volume) - self.driver.create_snapshot(test_snapshot_vmax) + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.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) + self.data.test_clone, + self.data.test_snapshot_vmax) + self.driver.delete_snapshot(self.data.test_snapshot_vmax) + self.driver.delete_volume(self.data.test_volume) def test_create_volume_snapshot_replica_failed(self): - self.driver.create_volume(test_volume) - self.driver.create_snapshot(test_snapshot) + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.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) + self.data.failed_snapshot_replica, + self.data.test_snapshot) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.test_volume) def test_create_volume_snapshot_sync_failed(self): - self.driver.create_volume(test_volume) - self.driver.create_snapshot(test_snapshot) + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.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) + self.data.failed_snapshot_sync, + self.data.test_snapshot) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.test_volume) def test_create_volume_clone_replica_failed(self): - self.driver.create_volume(test_volume) + self.driver.create_volume(self.data.test_volume) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, - failed_clone_replica, - test_volume) - self.driver.delete_volume(test_volume) + self.data.failed_clone_replica, + self.data.test_volume) + self.driver.delete_volume(self.data.test_volume) def test_create_volume_clone_sync_failed(self): - self.driver.create_volume(test_volume) + self.driver.create_volume(self.data.test_volume) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, - failed_clone_sync, - test_volume) - self.driver.delete_volume(test_volume) + self.data.failed_clone_sync, + self.data.test_volume) + self.driver.delete_volume(self.data.test_volume) def test_delete_volume_notfound(self): notfound_delete_vol = {} notfound_delete_vol['name'] = 'notfound_delete_vol' notfound_delete_vol['id'] = '10' + notfound_delete_vol['CreationClassName'] = 'Clar_StorageVolume' + notfound_delete_vol['SystemName'] = self.data.storage_system + notfound_delete_vol['DeviceID'] = notfound_delete_vol['id'] + notfound_delete_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + name = {} + name['classname'] = 'Clar_StorageVolume' + keys = {} + keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] + keys['SystemName'] = notfound_delete_vol['SystemName'] + keys['DeviceID'] = notfound_delete_vol['DeviceID'] + keys['SystemCreationClassName'] =\ + notfound_delete_vol['SystemCreationClassName'] + name['keybindings'] = keys + notfound_delete_vol['provider_location'] = str(name) self.driver.delete_volume(notfound_delete_vol) def test_delete_volume_failed(self): - self.driver.create_volume(failed_delete_vol) + self.driver.create_volume(self.data.failed_delete_vol) self.assertRaises(exception.VolumeBackendAPIException, self.driver.delete_volume, - failed_delete_vol) + self.data.failed_delete_vol) + + def test_extend_volume(self): + self.driver.create_volume(self.data.test_volume) + self.driver.extend_volume(self.data.test_volume, '10') + self.driver.create_volume(self.data.failed_extend_vol) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.extend_volume, + self.data.failed_extend_vol, + '10') def _cleanup(self): bExists = os.path.exists(self.config_file_path) @@ -764,3 +1085,240 @@ class EMCSMISISCSIDriverTestCase(test.TestCase): def tearDown(self): self._cleanup() super(EMCSMISISCSIDriverTestCase, self).tearDown() + + +class EMCSMISFCDriverTestCase(test.TestCase): + + def setUp(self): + + self.data = EMCSMISCommonData() + + self.tempdir = tempfile.mkdtemp() + super(EMCSMISFCDriverTestCase, self).setUp() + self.config_file_path = None + self.create_fake_config_file() + + configuration = mock.Mock() + configuration.cinder_emc_config_file = self.config_file_path + + self.stubs.Set(EMCSMISCommon, '_get_ecom_connection', + self.fake_ecom_connection) + instancename = FakeCIMInstanceName() + self.stubs.Set(EMCSMISCommon, '_getinstancename', + instancename.fake_getinstancename) + driver = EMCSMISFCDriver(configuration=configuration) + driver.db = FakeDB() + 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) + + self.config_file_path = self.tempdir + '/' + self.data.config_file_name + f = open(self.config_file_path, 'w') + doc.writexml(f) + f.close() + + def fake_ecom_connection(self): + conn = FakeEcomConnection() + return conn + + def test_get_volume_stats(self): + self.driver.get_volume_stats(True) + + def test_create_destroy(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_snapshot_destroy(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.test_snapshot) + self.driver.create_volume_from_snapshot( + self.data.test_clone, self.data.test_snapshot) + self.driver.create_cloned_volume( + self.data.test_clone3, self.data.test_volume) + self.driver.delete_volume(self.data.test_clone) + self.driver.delete_volume(self.data.test_clone3) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.test_volume) + + def test_map_unmap(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + + output = {'driver_volume_type': 'fibre_channel', + 'data': {'target_lun': 0, + 'target_wwn': ['1234567890123', '0987654321321'], + 'target_discovered': True}} + connection_info = self.driver.initialize_connection( + self.data.test_volume, + self.data.connector) + self.assertEqual(connection_info, output) + + self.driver.terminate_connection(self.data.test_volume, + self.data.connector) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_failed(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume, + self.data.test_failed_volume) + + def test_create_volume_snapshot_unsupported(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.test_snapshot_vmax) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + self.data.test_clone, + self.data.test_snapshot_vmax) + self.driver.delete_snapshot(self.data.test_snapshot_vmax) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_snapshot_replica_failed(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.test_snapshot) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + self.data.failed_snapshot_replica, + self.data.test_snapshot) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_snapshot_sync_failed(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.create_snapshot(self.data.test_snapshot) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + self.data.failed_snapshot_sync, + self.data.test_snapshot) + self.driver.delete_snapshot(self.data.test_snapshot) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_clone_replica_failed(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cloned_volume, + self.data.failed_clone_replica, + self.data.test_volume) + self.driver.delete_volume(self.data.test_volume) + + def test_create_volume_clone_sync_failed(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cloned_volume, + self.data.failed_clone_sync, + self.data.test_volume) + self.driver.delete_volume(self.data.test_volume) + + def test_delete_volume_notfound(self): + notfound_delete_vol = {} + notfound_delete_vol['name'] = 'notfound_delete_vol' + notfound_delete_vol['id'] = '10' + notfound_delete_vol['CreationClassName'] = 'Clar_StorageVolume' + notfound_delete_vol['SystemName'] = self.data.storage_system + notfound_delete_vol['DeviceID'] = notfound_delete_vol['id'] + notfound_delete_vol['SystemCreationClassName'] = 'Clar_StorageSystem' + name = {} + name['classname'] = 'Clar_StorageVolume' + keys = {} + keys['CreationClassName'] = notfound_delete_vol['CreationClassName'] + keys['SystemName'] = notfound_delete_vol['SystemName'] + keys['DeviceID'] = notfound_delete_vol['DeviceID'] + keys['SystemCreationClassName'] =\ + notfound_delete_vol['SystemCreationClassName'] + name['keybindings'] = keys + notfound_delete_vol['provider_location'] = str(name) + self.driver.delete_volume(notfound_delete_vol) + + def test_delete_volume_failed(self): + self.driver.create_volume(self.data.failed_delete_vol) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.delete_volume, + self.data.failed_delete_vol) + + def test_extend_volume(self): + self.data.test_volume['volume_type_id'] = None + self.driver.create_volume(self.data.test_volume) + self.driver.extend_volume(self.data.test_volume, '10') + self.driver.create_volume(self.data.failed_extend_vol) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.extend_volume, + self.data.failed_extend_vol, + '10') + + @mock.patch.object( + volume_types, + 'get_volume_type_extra_specs', + return_value={'storagetype:pool': 'gold', + 'storagetype:provisioning': 'thick'}) + def test_create_volume_with_volume_type(self, _mock_volume_type): + volume_with_vt = self.data.test_volume + volume_with_vt['volume_type_id'] = 1 + self.driver.create_volume(volume_with_vt) + + configservice = {'CreationClassName': + 'Clar_StorageConfigurationService', + 'SystemName': 'CLARiiON+APM00123456789'} + + pool = {'InstanceID': 'CLARiiON+APM00123456789+U+gold', + 'CreationClassName': 'Clar_UnifiedStoragePool'} + + volumesize = int(volume_with_vt['size']) * units.GiB + + storage_type = {'storagetype:provisioning': 'thick', + 'storagetype:pool': 'gold'} + + expected = [ + mock.call._get_storage_type(volume_with_vt), + mock.call._find_pool('gold'), + mock.call.get_provisioning(storage_type), + mock.call.InvokeMethod('CreateOrModifyElementFromStoragePool', + configservice, volume_with_vt['name'], + pool, + self.driver.common._getnum(2, '16'), + self.driver.common._getnum(volumesize, + '64'))] + + def _cleanup(self): + bExists = os.path.exists(self.config_file_path) + if bExists: + os.remove(self.config_file_path) + shutil.rmtree(self.tempdir) + + def tearDown(self): + self._cleanup() + super(EMCSMISFCDriverTestCase, self).tearDown() diff --git a/cinder/volume/drivers/emc/emc_smis_common.py b/cinder/volume/drivers/emc/emc_smis_common.py index f5aefeb7b..b15d76c1d 100644 --- a/cinder/volume/drivers/emc/emc_smis_common.py +++ b/cinder/volume/drivers/emc/emc_smis_common.py @@ -1,5 +1,4 @@ -# Copyright (c) 2012 EMC Corporation. -# Copyright (c) 2012 OpenStack Foundation +# Copyright (c) 2012 - 2014 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -29,6 +28,7 @@ from xml.dom.minidom import parseString from cinder import exception from cinder.openstack.common import log as logging from cinder import units +from cinder.volume import volume_types LOG = logging.getLogger(__name__) @@ -41,6 +41,9 @@ except ImportError: 'Install PyWBEM using the python-pywbem package.')) CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml' +EMC_ROOT = 'root/emc' +PROVISIONING = 'storagetype:provisioning' +POOL = 'storagetype:pool' emc_opts = [ cfg.StrOpt('cinder_emc_config_file', @@ -64,6 +67,7 @@ class EMCSMISCommon(): 'volume_backend_name': None} def __init__(self, prtcl, configuration=None): + self.protocol = prtcl self.configuration = configuration self.configuration.append_config_values(emc_opts) @@ -75,7 +79,6 @@ class EMCSMISCommon(): def create_volume(self, volume): """Creates a EMC(VMAX/VNX) volume.""" - LOG.debug(_('Entering create_volume.')) volumesize = int(volume['size']) * units.GiB volumename = volume['name'] @@ -86,14 +89,14 @@ class EMCSMISCommon(): self.conn = self._get_ecom_connection() - storage_type = self._get_storage_type() + storage_type = self._get_storage_type(volume) 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) + pool, storage_system = self._find_pool(storage_type[POOL]) LOG.debug(_('Create Volume: %(volume)s Pool: %(pool)s ' 'Storage System: %(storage_system)s') @@ -112,19 +115,22 @@ class EMCSMISCommon(): LOG.error(exception_message) raise exception.VolumeBackendAPIException(data=exception_message) + provisioning = self._get_provisioning(storage_type) + LOG.debug(_('Create Volume: %(name)s Method: ' 'CreateOrModifyElementFromStoragePool ConfigServicie: ' '%(service)s ElementName: %(name)s InPool: %(pool)s ' - 'ElementType: 5 Size: %(size)lu') + 'ElementType: %(provisioning)s Size: %(size)lu') % {'service': str(configservice), 'name': volumename, 'pool': str(pool), + 'provisioning': provisioning, 'size': volumesize}) rc, job = self.conn.InvokeMethod( 'CreateOrModifyElementFromStoragePool', configservice, ElementName=volumename, InPool=pool, - ElementType=self._getnum(5, '16'), + ElementType=self._getnum(provisioning, '16'), Size=self._getnum(volumesize, '64')) LOG.debug(_('Create Volume: %(volumename)s Return code: %(rc)lu') @@ -141,10 +147,28 @@ class EMCSMISCommon(): 'error': errordesc}) raise exception.VolumeBackendAPIException(data=errordesc) + # Find the newly created volume + associators = self.conn.Associators( + job['Job'], + resultClass='EMC_StorageVolume') + volpath = associators[0].path + name = {} + name['classname'] = volpath.classname + keys = {} + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + name['keybindings'] = keys + LOG.debug(_('Leaving create_volume: %(volumename)s ' - 'Return code: %(rc)lu') + 'Return code: %(rc)lu ' + 'volume instance: %(name)s') % {'volumename': volumename, - 'rc': rc}) + 'rc': rc, + 'name': name}) + + return name def create_volume_from_snapshot(self, volume, snapshot): """Creates a volume from a snapshot.""" @@ -227,14 +251,29 @@ class EMCSMISCommon(): raise exception.VolumeBackendAPIException( data=exception_message) + # Find the newly created volume + associators = self.conn.Associators( + job['Job'], + resultClass='EMC_StorageVolume') + volpath = associators[0].path + name = {} + name['classname'] = volpath.classname + keys = {} + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + name['keybindings'] = keys + 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}) + volume['provider_location'] = str(name) sync_name, storage_system = self._find_storage_sync_sv_sv( - volumename, snapshotname) + volume, snapshot) # Remove the Clone relationshop so it can be used as a regular lun # 8 - Detach operation @@ -282,6 +321,8 @@ class EMCSMISCommon(): 'snapshotname': snapshotname, 'rc': rc}) + return name + def create_cloned_volume(self, volume, src_vref): """Creates a clone of the specified volume.""" LOG.debug(_('Entering create_cloned_volume.')) @@ -351,14 +392,29 @@ class EMCSMISCommon(): raise exception.VolumeBackendAPIException( data=exception_message) + # Find the newly created volume + associators = self.conn.Associators( + job['Job'], + resultClass='EMC_StorageVolume') + volpath = associators[0].path + name = {} + name['classname'] = volpath.classname + keys = {} + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + name['keybindings'] = keys + LOG.debug(_('Create Cloned Volume: Volume: %(volumename)s ' 'Source Volume: %(srcname)s. Successfully cloned volume ' 'from source volume. Finding the clone relationship.') % {'volumename': volumename, 'srcname': srcname}) + volume['provider_location'] = str(name) sync_name, storage_system = self._find_storage_sync_sv_sv( - volumename, srcname) + volume, src_vref) # Remove the Clone relationshop so it can be used as a regular lun # 8 - Detach operation @@ -406,6 +462,8 @@ class EMCSMISCommon(): 'srcname': srcname, 'rc': rc}) + return name + def delete_volume(self, volume): """Deletes an EMC volume.""" LOG.debug(_('Entering delete_volume.')) @@ -467,7 +525,7 @@ class EMCSMISCommon(): % {'volumename': volumename, 'rc': rc}) - def create_snapshot(self, snapshot): + def create_snapshot(self, snapshot, volume): """Creates a snapshot.""" LOG.debug(_('Entering create_snapshot.')) @@ -479,10 +537,8 @@ class EMCSMISCommon(): self.conn = self._get_ecom_connection() - 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: ' @@ -532,11 +588,27 @@ class EMCSMISCommon(): raise exception.VolumeBackendAPIException( data=exception_message) + # Find the newly created volume + associators = self.conn.Associators( + job['Job'], + resultClass='EMC_StorageVolume') + volpath = associators[0].path + name = {} + name['classname'] = volpath.classname + keys = {} + keys['CreationClassName'] = volpath['CreationClassName'] + keys['SystemName'] = volpath['SystemName'] + keys['DeviceID'] = volpath['DeviceID'] + keys['SystemCreationClassName'] = volpath['SystemCreationClassName'] + name['keybindings'] = keys + 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): + return name + + def delete_snapshot(self, snapshot, volume): """Deletes a snapshot.""" LOG.debug(_('Entering delete_snapshot.')) @@ -554,7 +626,7 @@ class EMCSMISCommon(): 'volume': volumename}) sync_name, storage_system =\ - self._find_storage_sync_sv_sv(snapshotname, volumename, False) + self._find_storage_sync_sv_sv(snapshot, volume, False) if sync_name is None: LOG.error(_('Snapshot: %(snapshot)s: volume: %(volume)s ' 'not found on the array. No snapshot to delete.') @@ -614,22 +686,6 @@ class EMCSMISCommon(): 'snapshotname': snapshotname, 'rc': rc}) - 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}) - 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} - # Mapping method for VNX def _expose_paths(self, configservice, vol_instance, connector): @@ -854,22 +910,98 @@ class EMCSMISCommon(): self.conn = self._get_ecom_connection() self._unmap_lun(volume, connector) + def extend_volume(self, volume, new_size): + """Extends an existing volume.""" + LOG.debug(_('Entering extend_volume.')) + volumesize = int(new_size) * units.GiB + volumename = volume['name'] + + LOG.info(_('Extend Volume: %(volume)s New size: %(size)lu') + % {'volume': volumename, + 'size': volumesize}) + + self.conn = self._get_ecom_connection() + + storage_type = self._get_storage_type(volume) + + 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}) + + configservice = self._find_storage_configuration_service( + storage_system) + if configservice is None: + exception_message = (_("Error Extend Volume: %(volumename)s. " + "Storage Configuration Service not found.") + % {'volumename': volumename}) + LOG.error(exception_message) + raise exception.VolumeBackendAPIException(data=exception_message) + + provisioning = self._get_provisioning(storage_type) + + LOG.debug(_('Extend Volume: %(name)s Method: ' + 'CreateOrModifyElementFromStoragePool ConfigServicie: ' + '%(service)s ElementType: %(provisioning)s Size: %(size)lu' + 'Volume path: %(volumepath)s') + % {'service': str(configservice), + 'name': volumename, + 'provisioning': provisioning, + 'size': volumesize, + 'volumepath': vol_instance.path}) + + rc, job = self.conn.InvokeMethod( + 'CreateOrModifyElementFromStoragePool', + configservice, ElementType=self._getnum(provisioning, '16'), + Size=self._getnum(volumesize, '64'), + TheElement=vol_instance.path) + + LOG.debug(_('Extend 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 Extend Volume: %(volumename)s. ' + 'Return code: %(rc)lu. Error: %(error)s') + % {'volumename': volumename, + 'rc': rc, + 'error': errordesc}) + raise exception.VolumeBackendAPIException(data=errordesc) + + LOG.debug(_('Leaving extend_volume: %(volumename)s ' + 'Return code: %(rc)lu ') + % {'volumename': volumename, + 'rc': rc}) + def update_volume_stats(self): """Retrieve stats info.""" LOG.debug(_("Updating volume stats")) - self.conn = self._get_ecom_connection() - storage_type = self._get_storage_type() + self.stats['total_capacity_gb'] = 'unknown' + self.stats['free_capacity_gb'] = 'unknown' - pool, storagesystem = self._find_pool(storage_type, True) + return self.stats - self.stats['total_capacity_gb'] = pool['TotalManagedSpace'] - self.stats['free_capacity_gb'] = pool['RemainingManagedSpace'] + def _get_storage_type(self, volume, filename=None): + """Get storage type. - return self.stats + Look for user input volume type first. + If not available, fall back to finding it in conf file. + """ + specs = self._get_volumetype_extraspecs(volume) + if not specs: + specs = self._get_storage_type_conffile() + LOG.debug(_("Storage Type: %s") % (specs)) + return specs - def _get_storage_type(self, filename=None): + def _get_storage_type_conffile(self, filename=None): """Get the storage type from the config file.""" - if filename is None: + if filename == None: filename = self.configuration.cinder_emc_config_file file = open(filename, 'r') @@ -881,8 +1013,11 @@ class EMCSMISCommon(): storageType = storageTypes[0].toxml() storageType = storageType.replace('', '') storageType = storageType.replace('', '') - LOG.debug(_("Found Storage Type: %s") % (storageType)) - return storageType + LOG.debug(_("Found Storage Type in config file: %s") + % (storageType)) + specs = {} + specs[POOL] = storageType + return specs else: exception_message = (_("Storage type not found.")) LOG.error(exception_message) @@ -1085,30 +1220,13 @@ class EMCSMISCommon(): def _find_lun(self, volume): foundinstance = None - try: - device_id = volume['provider_location'] - except Exception: - device_id = None volumename = volume['name'] - - names = self.conn.EnumerateInstanceNames('EMC_StorageVolume') - - for n in names: - if device_id is not None: - if n['DeviceID'] == device_id: - vol_instance = self.conn.GetInstance(n) - foundinstance = vol_instance - break - else: - continue - - else: - vol_instance = self.conn.GetInstance(n) - if vol_instance['ElementName'] == volumename: - foundinstance = vol_instance - volume['provider_location'] = foundinstance['DeviceID'] - break + loc = volume['provider_location'] + name = eval(loc) + instancename = self._getinstancename(name['classname'], + name['keybindings']) + foundinstance = self.conn.GetInstance(instancename) if foundinstance is None: LOG.debug(_("Volume %(volumename)s not found on the array.") @@ -1121,33 +1239,24 @@ class EMCSMISCommon(): return foundinstance - def _find_storage_sync_sv_sv(self, snapshotname, volumename, + def _find_storage_sync_sv_sv(self, snapshot, volume, waitforsync=True): foundsyncname = None storage_system = None percent_synced = 0 + snapshotname = snapshot['name'] + volumename = volume['name'] LOG.debug(_("Source: %(volumename)s Target: %(snapshotname)s.") % {'volumename': volumename, 'snapshotname': snapshotname}) - names = self.conn.EnumerateInstanceNames( - 'SE_StorageSynchronized_SV_SV') - - for n in names: - snapshot_instance = self.conn.GetInstance(n['SyncedElement'], - LocalOnly=False) - if snapshotname != snapshot_instance['ElementName']: - continue - - vol_instance = self.conn.GetInstance(n['SystemElement'], - LocalOnly=False) - if vol_instance['ElementName'] == volumename: - foundsyncname = n - storage_system = vol_instance['SystemName'] - if waitforsync: - sync_instance = self.conn.GetInstance(n, LocalOnly=False) - percent_synced = sync_instance['PercentSynced'] - break + snapshot_instance = self._find_lun(snapshot) + volume_instance = self._find_lun(volume) + storage_system = volume_instance['SystemName'] + classname = 'SE_StorageSynchronized_SV_SV' + bindings = {'SyncedElement': snapshot_instance.path, + 'SystemElement': volume_instance.path} + foundsyncname = self._getinstancename(classname, bindings) if foundsyncname is None: LOG.debug(_("Source: %(volumename)s Target: %(snapshotname)s. " @@ -1293,7 +1402,7 @@ class EMCSMISCommon(): return foundCtrl # Find out how many volumes are mapped to a host - # associated to the LunMaskingSCSIProtocolController + # assoociated to the LunMaskingSCSIProtocolController def get_num_volumes_mapped(self, volume, connector): numVolumesMapped = 0 volumename = volume['name'] @@ -1510,6 +1619,18 @@ class EMCSMISCommon(): return result + def _getinstancename(self, classname, bindings): + instancename = None + try: + instancename = pywbem.CIMInstanceName( + classname, + namespace=EMC_ROOT, + keybindings=bindings) + except NameError: + instancename = None + + return instancename + # Find target WWNs def get_target_wwns(self, storage_system, connector): target_wwns = [] @@ -1543,8 +1664,8 @@ class EMCSMISCommon(): 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}) + if not any(d == wwn for d in target_wwns): + target_wwns.append(wwn) LOG.debug(_('Add target WWN: %s.') % wwn) LOG.debug(_('Target WWNs: %s.') % target_wwns) @@ -1569,3 +1690,29 @@ class EMCSMISCommon(): 'foundInstances': str(foundInstances)}) return foundInstances + + def _get_volumetype_extraspecs(self, volume): + specs = {} + type_id = volume['volume_type_id'] + if type_id is not None: + specs = volume_types.get_volume_type_extra_specs(type_id) + # If specs['storagetype:pool'] not defined, + # set specs to {} so we can ready from config file later + if POOL not in specs: + specs = {} + + return specs + + def _get_provisioning(self, storage_type): + # provisioning is thin (5) by default + provisioning = 5 + thick_str = 'thick' + try: + type_prov = storage_type[PROVISIONING] + if type_prov.lower() == thick_str.lower(): + provisioning = 2 + except KeyError: + # Default to thin if not defined + pass + + return provisioning diff --git a/cinder/volume/drivers/emc/emc_smis_fc.py b/cinder/volume/drivers/emc/emc_smis_fc.py new file mode 100644 index 000000000..742044937 --- /dev/null +++ b/cinder/volume/drivers/emc/emc_smis_fc.py @@ -0,0 +1,198 @@ +# Copyright (c) 2014 EMC Corporation. +# 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. +""" +FC Drivers for EMC VNX and VMAX arrays based on SMI-S. + +""" + +from cinder import context +from cinder.openstack.common import log as logging +from cinder.volume import driver +from cinder.volume.drivers.emc import emc_smis_common + +LOG = logging.getLogger(__name__) + + +class EMCSMISFCDriver(driver.FibreChannelDriver): + """EMC FC Drivers for VMAX and VNX using SMI-S. + + Version history: + 1.0.0 - Initial driver + 1.1.0 - Multiple pools and thick/thin provisioning, + performance enhancement. + """ + + VERSION = "1.1.0" + + def __init__(self, *args, **kwargs): + + super(EMCSMISFCDriver, self).__init__(*args, **kwargs) + self.common = emc_smis_common.EMCSMISCommon( + 'FC', + configuration=self.configuration) + + def check_for_setup_error(self): + pass + + def create_volume(self, volume): + """Creates a EMC(VMAX/VNX) volume.""" + volpath = self.common.create_volume(volume) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update + + def create_volume_from_snapshot(self, volume, snapshot): + """Creates a volume from a snapshot.""" + volpath = self.common.create_volume_from_snapshot(volume, snapshot) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update + + def create_cloned_volume(self, volume, src_vref): + """Creates a cloned volume.""" + volpath = self.common.create_cloned_volume(volume, src_vref) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update + + def delete_volume(self, volume): + """Deletes an EMC volume.""" + self.common.delete_volume(volume) + + def create_snapshot(self, snapshot): + """Creates a snapshot.""" + ctxt = context.get_admin_context() + volumename = snapshot['volume_name'] + index = volumename.index('-') + volumeid = volumename[index + 1:] + volume = self.db.volume_get(ctxt, volumeid) + + volpath = self.common.create_snapshot(snapshot, volume) + + model_update = {} + snapshot['provider_location'] = str(volpath) + model_update['provider_location'] = snapshot['provider_location'] + return model_update + + def delete_snapshot(self, snapshot): + """Deletes a snapshot.""" + ctxt = context.get_admin_context() + volumename = snapshot['volume_name'] + index = volumename.index('-') + volumeid = volumename[index + 1:] + volume = self.db.volume_get(ctxt, volumeid) + + self.common.delete_snapshot(snapshot, volume) + + def ensure_export(self, context, volume): + """Driver entry point to get the export info for an existing volume.""" + pass + + def create_export(self, context, volume): + """Driver entry point to get the export info for a new volume.""" + pass + + def remove_export(self, context, volume): + """Driver entry point to remove an export for a volume.""" + pass + + def check_for_export(self, context, volume_id): + """Make sure volume is exported.""" + pass + + def initialize_connection(self, volume, connector): + """Initializes the connection and returns connection info. + + Assign any created volume to a compute node/host so that it can be + used from that host. + + The driver returns a driver_volume_type of 'fibre_channel'. + The target_wwn can be a single entry or a list of wwns that + correspond to the list of remote wwn(s) that will export the volume. + Example return values: + + { + 'driver_volume_type': 'fibre_channel' + 'data': { + 'target_discovered': True, + 'target_lun': 1, + 'target_wwn': '1234567890123', + } + } + + or + + { + 'driver_volume_type': 'fibre_channel' + 'data': { + 'target_discovered': True, + 'target_lun': 1, + 'target_wwn': ['1234567890123', '0987654321321'], + } + } + + """ + device_info = self.common.initialize_connection(volume, + connector) + device_number = device_info['hostlunid'] + storage_system = device_info['storagesystem'] + ports = self.common.get_target_wwns(storage_system, connector) + + data = {'driver_volume_type': 'fibre_channel', + 'data': {'target_lun': device_number, + 'target_discovered': True, + 'target_wwn': ports}} + + LOG.debug(_('Return FC data: %(data)s.') + % {'data': data}) + + return data + + def terminate_connection(self, volume, connector, **kwargs): + """Disallow connection from connector.""" + self.common.terminate_connection(volume, connector) + + def extend_volume(self, volume, new_size): + """Extend an existing volume.""" + self.common.extend_volume(volume, new_size) + + def get_volume_stats(self, refresh=False): + """Get volume stats. + + If 'refresh' is True, run update the stats first. + """ + if refresh: + self.update_volume_stats() + + return self._stats + + def update_volume_stats(self): + """Retrieve stats info from volume group.""" + LOG.debug(_("Updating volume stats")) + data = self.common.update_volume_stats() + backend_name = self.configuration.safe_get('volume_backend_name') + data['volume_backend_name'] = backend_name or 'EMCSMISFCDriver' + data['storage_protocol'] = 'FC' + data['driver_version'] = self.VERSION + self._stats = data diff --git a/cinder/volume/drivers/emc/emc_smis_iscsi.py b/cinder/volume/drivers/emc/emc_smis_iscsi.py index 550d64ed4..9548d49c0 100644 --- a/cinder/volume/drivers/emc/emc_smis_iscsi.py +++ b/cinder/volume/drivers/emc/emc_smis_iscsi.py @@ -1,5 +1,4 @@ -# Copyright (c) 2012 EMC Corporation. -# Copyright (c) 2012 OpenStack Foundation +# Copyright (c) 2012 - 2014 EMC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -19,6 +18,7 @@ ISCSI Drivers for EMC VNX and VMAX arrays based on SMI-S. """ +from cinder import context from cinder import exception from cinder.openstack.common import log as logging from cinder.volume import driver @@ -28,9 +28,15 @@ LOG = logging.getLogger(__name__) class EMCSMISISCSIDriver(driver.ISCSIDriver): - """EMC ISCSI Drivers for VMAX and VNX using SMI-S.""" + """EMC ISCSI Drivers for VMAX and VNX using SMI-S. - VERSION = "1.0.0" + Version history: + 1.0.0 - Initial driver + 1.1.0 - Multiple pools and thick/thin provisioning, + performance enhancement. + """ + + VERSION = "1.1.0" def __init__(self, *args, **kwargs): @@ -44,15 +50,33 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver): def create_volume(self, volume): """Creates a EMC(VMAX/VNX) volume.""" - self.common.create_volume(volume) + volpath = self.common.create_volume(volume) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update def create_volume_from_snapshot(self, volume, snapshot): """Creates a volume from a snapshot.""" - self.common.create_volume_from_snapshot(volume, snapshot) + volpath = self.common.create_volume_from_snapshot(volume, snapshot) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update def create_cloned_volume(self, volume, src_vref): """Creates a cloned volume.""" - self.common.create_cloned_volume(volume, src_vref) + volpath = self.common.create_cloned_volume(volume, src_vref) + + ctxt = context.get_admin_context() + model_update = {} + volume['provider_location'] = str(volpath) + model_update['provider_location'] = volume['provider_location'] + return model_update def delete_volume(self, volume): """Deletes an EMC volume.""" @@ -60,11 +84,28 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver): def create_snapshot(self, snapshot): """Creates a snapshot.""" - self.common.create_snapshot(snapshot) + ctxt = context.get_admin_context() + volumename = snapshot['volume_name'] + index = volumename.index('-') + volumeid = volumename[index + 1:] + volume = self.db.volume_get(ctxt, volumeid) + + volpath = self.common.create_snapshot(snapshot, volume) + + model_update = {} + snapshot['provider_location'] = str(volpath) + model_update['provider_location'] = snapshot['provider_location'] + return model_update def delete_snapshot(self, snapshot): """Deletes a snapshot.""" - self.common.delete_snapshot(snapshot) + ctxt = context.get_admin_context() + volumename = snapshot['volume_name'] + index = volumename.index('-') + volumeid = volumename[index + 1:] + volume = self.db.volume_get(ctxt, volumeid) + + self.common.delete_snapshot(snapshot, volume) def ensure_export(self, context, volume): """Driver entry point to get the export info for an existing volume.""" @@ -72,7 +113,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver): def create_export(self, context, volume): """Driver entry point to get the export info for a new volume.""" - return self.common.create_export(context, volume) + pass def remove_export(self, context, volume): """Driver entry point to remove an export for a volume.""" @@ -220,6 +261,10 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver): """Disallow connection from connector.""" self.common.terminate_connection(volume, connector) + def extend_volume(self, volume, new_size): + """Extend an existing volume.""" + self.common.extend_volume(volume, new_size) + def get_volume_stats(self, refresh=False): """Get volume stats. @@ -237,4 +282,5 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver): backend_name = self.configuration.safe_get('volume_backend_name') data['volume_backend_name'] = backend_name or 'EMCSMISISCSIDriver' data['storage_protocol'] = 'iSCSI' + data['driver_version'] = self.VERSION self._stats = data -- 2.45.2