Add multiple pools support to VMAX driver
This patch adds multiple pools support to the VMAX driver. Also changed \ to () in a few places. implements blueprint emc-vmax-multiple-pools Change-Id: Iae0c658c808168fa5279b8dbcc6734632a98642d
This commit is contained in:
parent
f4a6c9fd5e
commit
3cf36e1f1d
File diff suppressed because it is too large
Load Diff
|
@ -29,6 +29,7 @@ from cinder.volume.drivers.emc import emc_vmax_masking
|
|||
from cinder.volume.drivers.emc import emc_vmax_provision
|
||||
from cinder.volume.drivers.emc import emc_vmax_provision_v3
|
||||
from cinder.volume.drivers.emc import emc_vmax_utils
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -44,6 +45,9 @@ except ImportError:
|
|||
CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml'
|
||||
CINDER_EMC_CONFIG_FILE_PREFIX = '/etc/cinder/cinder_emc_config_'
|
||||
CINDER_EMC_CONFIG_FILE_POSTFIX = '.xml'
|
||||
BACKENDNAME = 'volume_backend_name'
|
||||
PREFIXBACKENDNAME = 'capabilities:volume_backend_name'
|
||||
PORTGROUPNAME = 'portgroupname'
|
||||
EMC_ROOT = 'root/emc'
|
||||
POOL = 'storagetype:pool'
|
||||
ARRAY = 'storagetype:array'
|
||||
|
@ -54,7 +58,6 @@ STRIPECOUNT = 'storagetype:stripecount'
|
|||
MEMBERCOUNT = 'storagetype:membercount'
|
||||
STRIPED = 'striped'
|
||||
CONCATENATED = 'concatenated'
|
||||
SMI_VERSION_8 = 800
|
||||
# V3
|
||||
SLO = 'storagetype:slo'
|
||||
WORKLOAD = 'storagetype:workload'
|
||||
|
@ -88,14 +91,11 @@ class EMCVMAXCommon(object):
|
|||
'vendor_name': 'EMC',
|
||||
'volume_backend_name': None}
|
||||
|
||||
pool_info = {'pool_name': None,
|
||||
'fast_policy': None,
|
||||
'backend_name': None,
|
||||
'serial_number': None,
|
||||
'is_v3': False,
|
||||
'config_file': None}
|
||||
pool_info = {'backend_name': None,
|
||||
'config_file': None,
|
||||
'arrays_info': {}}
|
||||
|
||||
def __init__(self, prtcl, configuration=None):
|
||||
def __init__(self, prtcl, version, configuration=None):
|
||||
|
||||
if not pywbemAvailable:
|
||||
LOG.info(_LI(
|
||||
|
@ -114,6 +114,7 @@ class EMCVMAXCommon(object):
|
|||
self.fast = emc_vmax_fast.EMCVMAXFast(prtcl)
|
||||
self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl)
|
||||
self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl)
|
||||
self.version = version
|
||||
self._gather_info()
|
||||
|
||||
def _gather_info(self):
|
||||
|
@ -133,33 +134,10 @@ class EMCVMAXCommon(object):
|
|||
{'emcConfigFileName': self.pool_info['config_file'],
|
||||
'backendName': self.pool_info['backend_name']})
|
||||
|
||||
if self.conn is None:
|
||||
self._set_ecom_credentials(self.pool_info['config_file'])
|
||||
|
||||
self.pool_info['serial_number'] = (
|
||||
self.utils.parse_array_name_from_file(
|
||||
self.pool_info['arrays_info'] = (
|
||||
self.utils.parse_file_to_get_array_map(
|
||||
self.pool_info['config_file']))
|
||||
|
||||
if self.pool_info['serial_number'] is None:
|
||||
LOG.error(_LE(
|
||||
"Array Serial Number %(arrayName)s must be in the file "
|
||||
"%(emcConfigFileName)s."),
|
||||
{'arrayName': self.pool_info['serial_number'],
|
||||
'emcConfigFileName': self.pool_info['config_file']})
|
||||
|
||||
self.pool_info['pool_name'] = (
|
||||
self.utils.parse_pool_name_from_file(
|
||||
self.pool_info['config_file']))
|
||||
if self.pool_info['pool_name'] is None:
|
||||
LOG.error(_LE(
|
||||
"PoolName %(poolName)s must be in the file "
|
||||
"%(emcConfigFileName)s."),
|
||||
{'poolName': self.pool_info['pool_name'],
|
||||
'emcConfigFileName': self.pool_info['config_file']})
|
||||
|
||||
self.pool_info['is_v3'] = (
|
||||
self.utils.isArrayV3(self.conn, self.pool_info['serial_number']))
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a EMC(VMAX) volume from a pre-existing storage pool.
|
||||
|
||||
|
@ -215,6 +193,8 @@ class EMCVMAXCommon(object):
|
|||
{'volumeName': volumeName,
|
||||
'rc': rc,
|
||||
'name': volumeDict})
|
||||
# Adding version information
|
||||
volumeDict['version'] = self.version
|
||||
|
||||
return volumeDict
|
||||
|
||||
|
@ -229,7 +209,8 @@ class EMCVMAXCommon(object):
|
|||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
LOG.debug("Entering create_volume_from_snapshot.")
|
||||
extraSpecs = self._initial_setup(volume)
|
||||
snapshot['host'] = volume['host']
|
||||
extraSpecs = self._initial_setup(snapshot)
|
||||
self.conn = self._get_ecom_connection()
|
||||
snapshotInstance = self._find_lun(snapshot)
|
||||
storageSystem = snapshotInstance['SystemName']
|
||||
|
@ -249,7 +230,8 @@ class EMCVMAXCommon(object):
|
|||
self.provision.delete_clone_relationship(
|
||||
self.conn, repservice, syncName, extraSpecs)
|
||||
|
||||
return self._create_cloned_volume(volume, snapshot, False)
|
||||
snapshot['host'] = volume['host']
|
||||
return self._create_cloned_volume(volume, snapshot, extraSpecs, False)
|
||||
|
||||
def create_cloned_volume(self, cloneVolume, sourceVolume):
|
||||
"""Creates a clone of the specified volume.
|
||||
|
@ -258,7 +240,9 @@ class EMCVMAXCommon(object):
|
|||
:param sourceVolume: volume object
|
||||
:returns: cloneVolumeDict -- the cloned volume dictionary
|
||||
"""
|
||||
return self._create_cloned_volume(cloneVolume, sourceVolume, False)
|
||||
extraSpecs = self._initial_setup(sourceVolume)
|
||||
return self._create_cloned_volume(cloneVolume, sourceVolume,
|
||||
extraSpecs, False)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a EMC(VMAX) volume.
|
||||
|
@ -283,7 +267,8 @@ class EMCVMAXCommon(object):
|
|||
:param volume: volume Object to create snapshot from
|
||||
:returns: dict -- the cloned volume dictionary
|
||||
"""
|
||||
return self._create_cloned_volume(snapshot, volume, True)
|
||||
extraSpecs = self._initial_setup(volume)
|
||||
return self._create_cloned_volume(snapshot, volume, extraSpecs, True)
|
||||
|
||||
def delete_snapshot(self, snapshot, volume):
|
||||
"""Deletes a snapshot.
|
||||
|
@ -293,6 +278,7 @@ class EMCVMAXCommon(object):
|
|||
"""
|
||||
LOG.info(_LI("Delete Snapshot: %(snapshotName)s."),
|
||||
{'snapshotName': snapshot['name']})
|
||||
snapshot['host'] = volume['host']
|
||||
self._delete_snapshot(snapshot)
|
||||
|
||||
def _remove_members(self, controllerConfigService,
|
||||
|
@ -564,64 +550,76 @@ class EMCVMAXCommon(object):
|
|||
|
||||
def update_volume_stats(self):
|
||||
"""Retrieve stats info."""
|
||||
pools = []
|
||||
backendName = self.pool_info['backend_name']
|
||||
for arrayInfo in self.pool_info['arrays_info']:
|
||||
self._set_ecom_credentials(arrayInfo)
|
||||
# Check what type of array it is
|
||||
isV3 = self.utils.isArrayV3(self.conn, arrayInfo['SerialNumber'])
|
||||
if isV3:
|
||||
location_info, total_capacity_gb, free_capacity_gb = (
|
||||
self._update_srp_stats(arrayInfo))
|
||||
poolName = ("%(slo)s+%(poolName)s+%(array)s"
|
||||
% {'slo': arrayInfo['SLO'],
|
||||
'poolName': arrayInfo['PoolName'],
|
||||
'array': arrayInfo['SerialNumber']})
|
||||
else:
|
||||
# This is V2
|
||||
location_info, total_capacity_gb, free_capacity_gb = (
|
||||
self._update_pool_stats(backendName, arrayInfo))
|
||||
poolName = ("%(poolName)s+%(array)s"
|
||||
% {'poolName': arrayInfo['PoolName'],
|
||||
'array': arrayInfo['SerialNumber']})
|
||||
|
||||
if self.pool_info['is_v3']:
|
||||
location_info, total_capacity_gb, free_capacity_gb = (
|
||||
self._update_srp_stats(self.pool_info['config_file'],
|
||||
self.pool_info['serial_number'],
|
||||
self.pool_info['pool_name']))
|
||||
else:
|
||||
# This is V2.
|
||||
location_info, total_capacity_gb, free_capacity_gb = (
|
||||
self._update_pool_stats(self.pool_info['config_file'],
|
||||
self.pool_info['backend_name'],
|
||||
self.pool_info['serial_number'],
|
||||
self.pool_info['pool_name']))
|
||||
pool = {'pool_name': poolName,
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb,
|
||||
'reserved_percentage': 0,
|
||||
'QoS_support': False,
|
||||
'location_info': location_info,
|
||||
'consistencygroup_support': True}
|
||||
pools.append(pool)
|
||||
|
||||
data = {'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb,
|
||||
'reserved_percentage': 0,
|
||||
'QoS_support': False,
|
||||
data = {'vendor_name': "EMC",
|
||||
'driver_version': self.version,
|
||||
'storage_protocol': 'unknown',
|
||||
'volume_backend_name': self.pool_info['backend_name'] or
|
||||
self.__class__.__name__,
|
||||
'vendor_name': "EMC",
|
||||
'driver_version': self.VERSION,
|
||||
'storage_protocol': 'unknown',
|
||||
'location_info': location_info,
|
||||
'consistencygroup_support': True}
|
||||
# Use zero capacities here so we always use a pool.
|
||||
'total_capacity_gb': 0,
|
||||
'free_capacity_gb': 0,
|
||||
'reserved_percentage': 0,
|
||||
'pools': pools}
|
||||
|
||||
return data
|
||||
|
||||
def _update_srp_stats(self, emcConfigFileName, arrayName, poolName):
|
||||
def _update_srp_stats(self, arrayInfo):
|
||||
"""Update SRP stats.
|
||||
|
||||
:param emcConfigFileName: the EMC configuration file
|
||||
:param arrayName: the array
|
||||
:param poolName: the pool
|
||||
:param arrayInfo: array information
|
||||
:returns: location_info
|
||||
:returns: totalManagedSpaceGbs
|
||||
:returns: remainingManagedSpaceGbs
|
||||
"""
|
||||
|
||||
totalManagedSpaceGbs, remainingManagedSpaceGbs = (
|
||||
self.utils.get_srp_pool_stats(self.conn, arrayName, poolName))
|
||||
self.provisionv3.get_srp_pool_stats(self.conn,
|
||||
arrayInfo))
|
||||
|
||||
LOG.info(_LI(
|
||||
"Capacity stats for SRP pool %(poolName)s on array "
|
||||
"%(arrayName)s total_capacity_gb=%(total_capacity_gb)lu, "
|
||||
"free_capacity_gb=%(free_capacity_gb)lu."),
|
||||
{'poolName': poolName,
|
||||
'arrayName': arrayName,
|
||||
"free_capacity_gb=%(free_capacity_gb)lu"),
|
||||
{'poolName': arrayInfo['PoolName'],
|
||||
'arrayName': arrayInfo['SerialNumber'],
|
||||
'total_capacity_gb': totalManagedSpaceGbs,
|
||||
'free_capacity_gb': remainingManagedSpaceGbs})
|
||||
slo = self.utils.parse_slo_from_file(emcConfigFileName)
|
||||
workload = self.utils.parse_workload_from_file(emcConfigFileName)
|
||||
|
||||
location_info = ("%(arrayName)s#%(poolName)s#%(slo)s#%(workload)s"
|
||||
% {'arrayName': arrayName,
|
||||
'poolName': poolName,
|
||||
'slo': slo,
|
||||
'workload': workload})
|
||||
% {'arrayName': arrayInfo['SerialNumber'],
|
||||
'poolName': arrayInfo['PoolName'],
|
||||
'slo': arrayInfo['SLO'],
|
||||
'workload': arrayInfo['Workload']})
|
||||
|
||||
return location_info, totalManagedSpaceGbs, remainingManagedSpaceGbs
|
||||
|
||||
|
@ -1203,7 +1201,6 @@ class EMCVMAXCommon(object):
|
|||
# If there are no extra specs then the default case is assumed.
|
||||
if extraSpecs:
|
||||
configGroup = self.configuration.config_group
|
||||
|
||||
configurationFile = self._register_config_file_from_config_group(
|
||||
configGroup)
|
||||
|
||||
|
@ -1317,23 +1314,13 @@ class EMCVMAXCommon(object):
|
|||
|
||||
if isinstance(loc, six.string_types):
|
||||
name = eval(loc)
|
||||
keys = name['keybindings']
|
||||
systemName = keys['SystemName']
|
||||
|
||||
prefix1 = 'SYMMETRIX+'
|
||||
prefix2 = 'SYMMETRIX-+-'
|
||||
smiversion = self.utils.get_smi_version(self.conn)
|
||||
if smiversion > SMI_VERSION_8 and prefix1 in systemName:
|
||||
keys['SystemName'] = systemName.replace(prefix1, prefix2)
|
||||
name['keybindings'] = keys
|
||||
|
||||
instancename = self.utils.get_instance_name(
|
||||
name['classname'], name['keybindings'])
|
||||
|
||||
# Handle the case where volume cannot be found.
|
||||
try:
|
||||
foundVolumeinstance = self.conn.GetInstance(instancename)
|
||||
except Exception:
|
||||
foundVolumeinstance = None
|
||||
foundVolumeinstance = self.utils.get_existing_instance(
|
||||
self.conn, instancename)
|
||||
|
||||
if foundVolumeinstance is None:
|
||||
LOG.debug("Volume %(volumename)s not found on the array.",
|
||||
|
@ -1566,7 +1553,6 @@ class EMCVMAXCommon(object):
|
|||
:returns: string -- configurationFile - name of the configuration file
|
||||
"""
|
||||
if configGroupName is None:
|
||||
self._set_ecom_credentials(CINDER_EMC_CONFIG_FILE)
|
||||
return CINDER_EMC_CONFIG_FILE
|
||||
if hasattr(self.configuration, 'cinder_emc_config_file'):
|
||||
configurationFile = self.configuration.cinder_emc_config_file
|
||||
|
@ -1586,15 +1572,6 @@ class EMCVMAXCommon(object):
|
|||
'configGroupName': configGroupName,
|
||||
'postfix': CINDER_EMC_CONFIG_FILE_POSTFIX}))
|
||||
|
||||
self._set_ecom_credentials(configurationFile)
|
||||
return configurationFile
|
||||
|
||||
def _set_ecom_credentials(self, configurationFile):
|
||||
"""Given the configuration file set the ecom credentials.
|
||||
|
||||
:param configurationFile: name of the file (String)
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
if os.path.isfile(configurationFile):
|
||||
LOG.debug("Configuration file : %(configurationFile)s exists.",
|
||||
{'configurationFile': configurationFile})
|
||||
|
@ -1605,10 +1582,21 @@ class EMCVMAXCommon(object):
|
|||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
ip, port = self.utils.get_ecom_server(configurationFile)
|
||||
self.user, self.passwd = self.utils.get_ecom_cred(configurationFile)
|
||||
self.ecomUseSSL, self.ecomCACert, self.ecomNoVerification = (
|
||||
self.utils.get_ecom_cred_SSL(configurationFile))
|
||||
return configurationFile
|
||||
|
||||
def _set_ecom_credentials(self, arrayInfo):
|
||||
"""Given the array record set the ecom credentials.
|
||||
|
||||
:param arrayInfo: record
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
ip = arrayInfo['EcomServerIp']
|
||||
port = arrayInfo['EcomServerPort']
|
||||
self.user = arrayInfo['EcomUserName']
|
||||
self.passwd = arrayInfo['EcomPassword']
|
||||
self.ecomUseSSL = arrayInfo['EcomUseSSL']
|
||||
self.ecomCACert = arrayInfo['EcomCACert']
|
||||
self.ecomNoVerification = arrayInfo['EcomNoVerification']
|
||||
ip_port = ("%(ip)s:%(port)s"
|
||||
% {'ip': ip,
|
||||
'port': port})
|
||||
|
@ -1641,33 +1629,34 @@ class EMCVMAXCommon(object):
|
|||
self._set_config_file_and_get_extra_specs(
|
||||
volume, volumeTypeId))
|
||||
|
||||
arrayName = self.utils.parse_array_name_from_file(
|
||||
pool = self._validate_pool(volume)
|
||||
LOG.debug("Pool returned is %(pool)s.",
|
||||
{'pool': pool})
|
||||
arrayInfo = self.utils.parse_file_to_get_array_map(
|
||||
configurationFile)
|
||||
if arrayName is None:
|
||||
poolRecord = self.utils.extract_record(arrayInfo, pool)
|
||||
|
||||
if not poolRecord:
|
||||
exceptionMessage = (_(
|
||||
"The array cannot be null. The pool must be configured "
|
||||
"either as a cinder extra spec for multi-backend or in "
|
||||
"the EMC configuration file for the default case."))
|
||||
LOG.error(exceptionMessage)
|
||||
"Unable to get corresponding record for pool."))
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
isV3 = self.utils.isArrayV3(self.conn, arrayName)
|
||||
self._set_ecom_credentials(poolRecord)
|
||||
isV3 = self.utils.isArrayV3(
|
||||
self.conn, poolRecord['SerialNumber'])
|
||||
|
||||
if isV3:
|
||||
extraSpecs = self._set_v3_extra_specs(
|
||||
configurationFile, arrayName, extraSpecs)
|
||||
extraSpecs = self._set_v3_extra_specs(extraSpecs, poolRecord)
|
||||
else:
|
||||
# V2 extra specs.
|
||||
extraSpecs = self._set_v2_extra_specs(
|
||||
configurationFile, arrayName, extraSpecs)
|
||||
# V2 extra specs
|
||||
extraSpecs = self._set_v2_extra_specs(extraSpecs, poolRecord)
|
||||
except Exception:
|
||||
import sys
|
||||
exceptionMessage = (_(
|
||||
"Unable to get configuration information necessary to create "
|
||||
"a volume. Please check that there is a configuration file "
|
||||
"for each config group, if multi-backend is enabled. "
|
||||
"The file should be in the following format "
|
||||
"/etc/cinder/cinder_emc_config_<CONFIG_GROUP>.xml."))
|
||||
"Unable to get configuration information necessary to "
|
||||
"create a volume: %(errorMessage)s.")
|
||||
% {'errorMessage': sys.exc_info()[1]})
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
return extraSpecs
|
||||
|
@ -1732,9 +1721,7 @@ class EMCVMAXCommon(object):
|
|||
% {'shortHostName': shortHostName,
|
||||
'poolName': poolName,
|
||||
'protocol': protocol}))
|
||||
maskingViewDict['fastPolicy'] = (
|
||||
self.utils.parse_fast_policy_name_from_file(
|
||||
self.configuration.cinder_emc_config_file))
|
||||
maskingViewDict['fastPolicy'] = extraSpecs[FASTPOLICY]
|
||||
|
||||
maskingViewDict['sgGroupName'] = ("%(prefix)s-SG"
|
||||
% {'prefix': prefix})
|
||||
|
@ -1749,9 +1736,7 @@ class EMCVMAXCommon(object):
|
|||
self.utils.find_controller_configuration_service(
|
||||
self.conn, storageSystemName))
|
||||
# The portGroup is gotten from emc xml config file.
|
||||
maskingViewDict['pgGroupName'] = (
|
||||
self.utils.parse_file_to_get_port_group_name(
|
||||
self.configuration.cinder_emc_config_file))
|
||||
maskingViewDict['pgGroupName'] = extraSpecs[PORTGROUPNAME]
|
||||
|
||||
maskingViewDict['igGroupName'] = (
|
||||
("OS-%(shortHostName)s-%(protocol)s-IG"
|
||||
|
@ -1843,8 +1828,8 @@ class EMCVMAXCommon(object):
|
|||
if 'True' in isVolumeBound:
|
||||
appendVolumeInstance = (
|
||||
self._unbind_and_get_volume_from_storage_pool(
|
||||
conn, storageConfigService, appendVolumeInstance.path,
|
||||
'appendVolume', extraSpecs))
|
||||
conn, storageConfigService, assocPoolInstanceName,
|
||||
appendVolumeInstance.path, 'appendVolume', extraSpecs))
|
||||
|
||||
return appendVolumeInstance
|
||||
|
||||
|
@ -1870,33 +1855,27 @@ class EMCVMAXCommon(object):
|
|||
return volumeInstance
|
||||
|
||||
def _unbind_and_get_volume_from_storage_pool(
|
||||
self, conn, storageConfigService, volumeInstanceName,
|
||||
volumeName, extraSpecs):
|
||||
self, conn, storageConfigService, poolInstanceName,
|
||||
volumeInstanceName, volumeName, extraSpecs):
|
||||
"""Unbind a volume from a pool and return the unbound volume.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageConfigService: the storage config service instance name
|
||||
:param poolInstanceName: the pool instance name
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param volumeName: string the volumeName
|
||||
:param extraSpecs: extra specifications
|
||||
:returns: unboundVolumeInstance -- the unbound volume instance
|
||||
"""
|
||||
|
||||
rc, job = (
|
||||
_rc, job = (
|
||||
self.provision.unbind_volume_from_storage_pool(
|
||||
conn, storageConfigService, volumeInstanceName,
|
||||
conn, storageConfigService, poolInstanceName,
|
||||
volumeInstanceName,
|
||||
volumeName, extraSpecs))
|
||||
# Check that the volume is unbound
|
||||
volumeInstance = conn.GetInstance(volumeInstanceName)
|
||||
isVolumeBound = self.utils.is_volume_bound_to_pool(
|
||||
conn, volumeInstance)
|
||||
if 'False' not in isVolumeBound:
|
||||
exceptionMessage = (_(
|
||||
"Failed to unbind volume: %(volume)s")
|
||||
% {'volume': volumeInstanceName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
volumeDict = self.provision.get_volume_dict_from_job(conn, job['Job'])
|
||||
volumeInstance = self.utils.find_volume_instance(
|
||||
self.conn, volumeDict, volumeName)
|
||||
return volumeInstance
|
||||
|
||||
def _modify_and_get_composite_volume_instance(
|
||||
|
@ -1964,16 +1943,16 @@ class EMCVMAXCommon(object):
|
|||
return defaultStorageGroupInstanceName
|
||||
|
||||
def _create_cloned_volume(
|
||||
self, cloneVolume, sourceVolume, isSnapshot=False):
|
||||
self, cloneVolume, sourceVolume, extraSpecs, isSnapshot=False):
|
||||
"""Create a clone volume from the source volume.
|
||||
|
||||
:param cloneVolume: clone volume
|
||||
:param sourceVolume: source of the clone volume
|
||||
:param extraSpecs: extra specs
|
||||
:param isSnapshot: boolean -- Defaults to False
|
||||
:returns: dict -- cloneDict the cloned volume dictionary
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
extraSpecs = self._initial_setup(cloneVolume)
|
||||
|
||||
sourceName = sourceVolume['name']
|
||||
cloneName = cloneVolume['name']
|
||||
|
||||
|
@ -2035,6 +2014,8 @@ class EMCVMAXCommon(object):
|
|||
{'cloneName': cloneName,
|
||||
'sourceName': sourceName,
|
||||
'rc': rc})
|
||||
# Adding version information
|
||||
cloneDict['version'] = self.version
|
||||
|
||||
return cloneDict
|
||||
|
||||
|
@ -2823,28 +2804,6 @@ class EMCVMAXCommon(object):
|
|||
storageConfigService = self.utils.find_storage_configuration_service(
|
||||
self.conn, storageSystemName)
|
||||
|
||||
# Check the SLO range.
|
||||
maximumVolumeSize, minimumVolumeSize = (
|
||||
self.provisionv3.get_volume_range(
|
||||
self.conn, storageConfigService, poolInstanceName,
|
||||
extraSpecs[SLO], extraSpecs[WORKLOAD],
|
||||
extraSpecs))
|
||||
if not self.utils.is_in_range(
|
||||
volumeSize, maximumVolumeSize, minimumVolumeSize):
|
||||
LOG.warning(_LW(
|
||||
"Volume: %(volume)s with size: %(volumeSize)s bits "
|
||||
"is not in the Performance Capacity range: "
|
||||
"%(minimumVolumeSize)s-%(maximumVolumeSize)s bits. "
|
||||
"for SLO:%(slo)s and workload:%(workload)s. "
|
||||
"Unpredictable results may occur."),
|
||||
{'volume': volumeName,
|
||||
'volumeSize': volumeSize,
|
||||
'minimumVolumeSize': minimumVolumeSize,
|
||||
'maximumVolumeSize': maximumVolumeSize,
|
||||
'slo': extraSpecs[SLO],
|
||||
'workload': extraSpecs[WORKLOAD]
|
||||
})
|
||||
|
||||
# A volume created without specifying a storage group during
|
||||
# creation time is allocated from the default SRP pool and
|
||||
# assigned the optimized SLO.
|
||||
|
@ -2979,7 +2938,6 @@ class EMCVMAXCommon(object):
|
|||
storageGroupName))
|
||||
|
||||
storageSystemName = volumeInstance['SystemName']
|
||||
|
||||
if not isValid:
|
||||
LOG.error(_LE(
|
||||
"Volume %(name)s is not suitable for storage "
|
||||
|
@ -3022,6 +2980,7 @@ class EMCVMAXCommon(object):
|
|||
controllerConfigService = (
|
||||
self.utils.find_controller_configuration_service(
|
||||
self.conn, storageSystemName))
|
||||
|
||||
defaultSgName = self.utils.get_v3_storage_group_name(
|
||||
extraSpecs[POOL], extraSpecs[SLO], extraSpecs[WORKLOAD])
|
||||
|
||||
|
@ -3122,83 +3081,72 @@ class EMCVMAXCommon(object):
|
|||
return False
|
||||
|
||||
def _update_pool_stats(
|
||||
self, emcConfigFileName, backendName, arrayName, poolName):
|
||||
self, backendName, arrayInfo):
|
||||
"""Update pool statistics (V2).
|
||||
|
||||
:param emcConfigFileName: the EMC configuration file
|
||||
:param backendName: the backend name
|
||||
:param arrayName: the array name
|
||||
:param poolName: the pool name
|
||||
:param arrayInfo: the arrayInfo
|
||||
:returns: location_info, total_capacity_gb, free_capacity_gb
|
||||
"""
|
||||
# This value can be None.
|
||||
fastPolicyName = self.utils.parse_fast_policy_name_from_file(
|
||||
emcConfigFileName)
|
||||
if fastPolicyName is not None:
|
||||
|
||||
if arrayInfo['FastPolicy']:
|
||||
LOG.debug(
|
||||
"Fast policy %(fastPolicyName)s is enabled on %(arrayName)s.",
|
||||
{'fastPolicyName': fastPolicyName,
|
||||
'arrayName': arrayName})
|
||||
{'fastPolicyName': arrayInfo['FastPolicy'],
|
||||
'arrayName': arrayInfo['SerialNumber']})
|
||||
else:
|
||||
LOG.debug(
|
||||
"No Fast policy for Array:%(arrayName)s "
|
||||
"backend:%(backendName)s.",
|
||||
{'arrayName': arrayName,
|
||||
{'arrayName': arrayInfo['SerialNumber'],
|
||||
'backendName': backendName})
|
||||
|
||||
storageSystemInstanceName = self.utils.find_storageSystem(
|
||||
self.conn, arrayName)
|
||||
self.conn, arrayInfo['SerialNumber'])
|
||||
isTieringPolicySupported = (
|
||||
self.fast.is_tiering_policy_enabled_on_storage_system(
|
||||
self.conn, storageSystemInstanceName))
|
||||
|
||||
if (fastPolicyName is not None and
|
||||
isTieringPolicySupported is True): # FAST enabled.
|
||||
if (arrayInfo['FastPolicy'] is not None and
|
||||
isTieringPolicySupported is True): # FAST enabled
|
||||
total_capacity_gb, free_capacity_gb = (
|
||||
self.fast.get_capacities_associated_to_policy(
|
||||
self.conn, arrayName, fastPolicyName))
|
||||
self.conn, arrayInfo['SerialNumber'],
|
||||
arrayInfo['FastPolicy']))
|
||||
LOG.info(_LI(
|
||||
"FAST: capacity stats for policy %(fastPolicyName)s on "
|
||||
"array: %(arrayName)s total_capacity_gb=%(total_capacity_gb)lu"
|
||||
", free_capacity_gb=%(free_capacity_gb)lu."),
|
||||
{'fastPolicyName': fastPolicyName,
|
||||
'arrayName': arrayName,
|
||||
"FAST: capacity stats for policy %(fastPolicyName)s on array "
|
||||
"%(arrayName)s. total_capacity_gb=%(total_capacity_gb)lu, "
|
||||
"free_capacity_gb=%(free_capacity_gb)lu."),
|
||||
{'fastPolicyName': arrayInfo['FastPolicy'],
|
||||
'arrayName': arrayInfo['SerialNumber'],
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb})
|
||||
else: # NON-FAST
|
||||
total_capacity_gb, free_capacity_gb = (
|
||||
self.utils.get_pool_capacities(self.conn, poolName, arrayName))
|
||||
self.utils.get_pool_capacities(self.conn,
|
||||
arrayInfo['PoolName'],
|
||||
arrayInfo['SerialNumber']))
|
||||
LOG.info(_LI(
|
||||
"NON-FAST: capacity stats for pool %(poolName)s on array: "
|
||||
"NON-FAST: capacity stats for pool %(poolName)s on array "
|
||||
"%(arrayName)s total_capacity_gb=%(total_capacity_gb)lu, "
|
||||
"free_capacity_gb=%(free_capacity_gb)lu."),
|
||||
{'poolName': poolName,
|
||||
'arrayName': arrayName,
|
||||
{'poolName': arrayInfo['PoolName'],
|
||||
'arrayName': arrayInfo['SerialNumber'],
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb})
|
||||
|
||||
if poolName is None:
|
||||
LOG.debug("Unable to get the poolName for location_info.")
|
||||
if arrayName is None:
|
||||
LOG.debug("Unable to get the arrayName for location_info.")
|
||||
if fastPolicyName is None:
|
||||
LOG.debug("FAST is not enabled for this configuration: "
|
||||
"%(emcConfigFileName)s.",
|
||||
{'emcConfigFileName': emcConfigFileName})
|
||||
|
||||
location_info = ("%(arrayName)s#%(poolName)s#%(policyName)s"
|
||||
% {'arrayName': arrayName,
|
||||
'poolName': poolName,
|
||||
'policyName': fastPolicyName})
|
||||
% {'arrayName': arrayInfo['SerialNumber'],
|
||||
'poolName': arrayInfo['PoolName'],
|
||||
'policyName': arrayInfo['FastPolicy']})
|
||||
|
||||
return location_info, total_capacity_gb, free_capacity_gb
|
||||
|
||||
def _set_v2_extra_specs(self, configurationFile, arrayName, extraSpecs):
|
||||
def _set_v2_extra_specs(self, extraSpecs, poolRecord):
|
||||
"""Set the VMAX V2 extra specs.
|
||||
|
||||
:param configurationFile: the EMC configuration file
|
||||
:param arrayName: the array serial number
|
||||
:param extraSpecs: extra specifications
|
||||
:param poolRecord: pool record
|
||||
:returns: dict -- the extraSpecs
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
|
@ -3217,29 +3165,14 @@ class EMCVMAXCommon(object):
|
|||
extraSpecs[COMPOSITETYPE] = CONCATENATED
|
||||
LOG.debug("StripedMetaCount is not in the extra specs.")
|
||||
|
||||
poolName = self.utils.parse_pool_name_from_file(configurationFile)
|
||||
if poolName is None:
|
||||
exceptionMessage = (_(
|
||||
"The pool cannot be null. The pool must be configured "
|
||||
"either in the extra specs or in the EMC configuration "
|
||||
"file corresponding to the Volume Type."))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
# Get the FAST policy from the file. This value can be None if the
|
||||
# user doesn't want to associate with any FAST policy.
|
||||
fastPolicyName = self.utils.parse_fast_policy_name_from_file(
|
||||
configurationFile)
|
||||
if fastPolicyName is not None:
|
||||
if poolRecord['FastPolicy']:
|
||||
LOG.debug("The fast policy name is: %(fastPolicyName)s.",
|
||||
{'fastPolicyName': fastPolicyName})
|
||||
|
||||
extraSpecs[POOL] = poolName
|
||||
extraSpecs[ARRAY] = arrayName
|
||||
extraSpecs[FASTPOLICY] = fastPolicyName
|
||||
{'fastPolicyName': poolRecord['FastPolicy']})
|
||||
extraSpecs[FASTPOLICY] = poolRecord['FastPolicy']
|
||||
extraSpecs[ISV3] = False
|
||||
extraSpecs = self._get_job_extra_specs(configurationFile, extraSpecs)
|
||||
extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord)
|
||||
|
||||
LOG.debug("Pool is: %(pool)s "
|
||||
"Array is: %(array)s "
|
||||
|
@ -3253,27 +3186,21 @@ class EMCVMAXCommon(object):
|
|||
'memberCount': extraSpecs[MEMBERCOUNT]})
|
||||
return extraSpecs
|
||||
|
||||
def _set_v3_extra_specs(self, configurationFile, arrayName, extraSpecs):
|
||||
def _set_v3_extra_specs(self, extraSpecs, poolRecord):
|
||||
"""Set the VMAX V3 extra specs.
|
||||
|
||||
If SLO or workload are not specified then the default
|
||||
values are NONE and the Optimized SLO will be assigned to the
|
||||
volume.
|
||||
|
||||
:param configurationFile: the EMC configuration file
|
||||
:param arrayName: the array serial number
|
||||
:returns: dict -- the extraSpecs
|
||||
:param extraSpecs: extra specifications
|
||||
:param poolRecord: pool record
|
||||
:returns: dict -- the extra specifications dictionary
|
||||
"""
|
||||
extraSpecs[SLO] = self.utils.parse_slo_from_file(
|
||||
configurationFile)
|
||||
extraSpecs[WORKLOAD] = self.utils.parse_workload_from_file(
|
||||
configurationFile)
|
||||
extraSpecs[POOL] = self.utils.parse_pool_name_from_file(
|
||||
configurationFile)
|
||||
extraSpecs[ARRAY] = arrayName
|
||||
extraSpecs[SLO] = poolRecord['SLO']
|
||||
extraSpecs[WORKLOAD] = poolRecord['Workload']
|
||||
extraSpecs[ISV3] = True
|
||||
extraSpecs = self._get_job_extra_specs(configurationFile, extraSpecs)
|
||||
|
||||
extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord)
|
||||
LOG.debug("Pool is: %(pool)s "
|
||||
"Array is: %(array)s "
|
||||
"SLO is: %(slo)s "
|
||||
|
@ -3284,27 +3211,30 @@ class EMCVMAXCommon(object):
|
|||
'workload': extraSpecs[WORKLOAD]})
|
||||
return extraSpecs
|
||||
|
||||
def _get_job_extra_specs(self, configurationFile, extraSpecs):
|
||||
"""Get user defined extra specs around job intervals and retries.
|
||||
def _set_common_extraSpecs(self, extraSpecs, poolRecord):
|
||||
"""Set common extra specs.
|
||||
|
||||
:param configurationFile: the EMC configuration file
|
||||
:param extraSpecs: extraSpecs (in)
|
||||
:returns: extraSpecs (out)
|
||||
The extraSpecs are common to v2 and v3
|
||||
|
||||
:param extraSpecs: extra specifications
|
||||
:param poolRecord: pool record
|
||||
:returns: dict -- the extra specifications dictionary
|
||||
"""
|
||||
intervalInSecs = self.utils.parse_interval_from_file(
|
||||
configurationFile)
|
||||
if intervalInSecs is not None:
|
||||
extraSpecs[POOL] = poolRecord['PoolName']
|
||||
extraSpecs[ARRAY] = poolRecord['SerialNumber']
|
||||
extraSpecs[PORTGROUPNAME] = poolRecord['PortGroup']
|
||||
if 'Interval' in poolRecord and poolRecord['Interval']:
|
||||
extraSpecs[INTERVAL] = poolRecord['Interval']
|
||||
LOG.debug("The user defined interval is : %(intervalInSecs)s.",
|
||||
{'intervalInSecs': intervalInSecs})
|
||||
extraSpecs[INTERVAL] = intervalInSecs
|
||||
|
||||
retries = self.utils.parse_retries_from_file(
|
||||
configurationFile)
|
||||
if retries is not None:
|
||||
{'intervalInSecs': poolRecord['Interval']})
|
||||
else:
|
||||
LOG.debug("Interval not overridden, default of 10 assumed.")
|
||||
if 'Retries' in poolRecord and poolRecord['Retries']:
|
||||
extraSpecs[RETRIES] = poolRecord['Retries']
|
||||
LOG.debug("The user defined retries is : %(retries)s.",
|
||||
{'retries': retries})
|
||||
extraSpecs[RETRIES] = retries
|
||||
|
||||
{'retries': poolRecord['Retries']})
|
||||
else:
|
||||
LOG.debug("Retries not overridden, default of 60 assumed.")
|
||||
return extraSpecs
|
||||
|
||||
def _delete_from_pool(self, storageConfigService, volumeInstance,
|
||||
|
@ -3348,8 +3278,12 @@ class EMCVMAXCommon(object):
|
|||
controllerConfigurationService,
|
||||
volumeInstance.path, volumeName, extraSpecs)
|
||||
|
||||
LOG.debug("Deleting Volume: %(name)s with deviceId: %(deviceId)s.",
|
||||
{'name': volumeName,
|
||||
LOG.debug("Delete Volume: %(name)s Method: EMCReturnToStoragePool "
|
||||
"ConfigService: %(service)s TheElement: %(vol_instance)s "
|
||||
"DeviceId: %(deviceId)s.",
|
||||
{'service': storageConfigService,
|
||||
'name': volumeName,
|
||||
'vol_instance': volumeInstance.path,
|
||||
'deviceId': deviceId})
|
||||
try:
|
||||
rc = self.provision.delete_volume_from_pool(
|
||||
|
@ -3382,7 +3316,6 @@ class EMCVMAXCommon(object):
|
|||
{'volumeName': volumeName})
|
||||
LOG.exception(errorMessage)
|
||||
raise exception.VolumeBackendAPIException(data=errorMessage)
|
||||
|
||||
return rc
|
||||
|
||||
def _delete_from_pool_v3(self, storageConfigService, volumeInstance,
|
||||
|
@ -3476,9 +3409,9 @@ class EMCVMAXCommon(object):
|
|||
else: # Composite volume with meta device members.
|
||||
# Check if the meta members capacity.
|
||||
metaMemberInstanceNames = (
|
||||
self.utils.get_composite_elements(
|
||||
self.conn, sourceInstance))
|
||||
volumeCapacities = self.utils.get_meta_members_capacity_in_byte(
|
||||
self.utils.get_meta_members_of_composite_volume(
|
||||
self.conn, metaHeadInstanceName))
|
||||
volumeCapacities = self.utils.get_meta_members_capacity_in_bit(
|
||||
self.conn, metaMemberInstanceNames)
|
||||
LOG.debug("Volume capacities: %(metasizes)s.",
|
||||
{'metasizes': volumeCapacities})
|
||||
|
@ -3741,9 +3674,6 @@ class EMCVMAXCommon(object):
|
|||
# Default operation 8: Detach for clone.
|
||||
operation = self.utils.get_num(8, '16')
|
||||
|
||||
# Create target volume
|
||||
extraSpecs = self._initial_setup(cloneVolume)
|
||||
|
||||
numOfBlocks = sourceInstance['NumberOfBlocks']
|
||||
blockSize = sourceInstance['BlockSize']
|
||||
volumeSizeInbits = numOfBlocks * blockSize
|
||||
|
@ -3904,6 +3834,67 @@ class EMCVMAXCommon(object):
|
|||
extraSpecs)
|
||||
return rc
|
||||
|
||||
def _validate_pool(self, volume):
|
||||
"""Get the pool from volume['host'].
|
||||
|
||||
There may be backward compatibiliy concerns, so putting in a
|
||||
check to see if a version has been added to provider_location.
|
||||
If it has, we know we are at the current version, if not, we
|
||||
assume it was created pre 'Pool Aware Scheduler' feature.
|
||||
|
||||
:param volume: the volume Object
|
||||
:returns: string -- pool
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
pool = None
|
||||
# Volume is None in CG ops.
|
||||
if volume is None:
|
||||
return pool
|
||||
|
||||
# This check is for all operations except a create.
|
||||
# On a create provider_location is None
|
||||
try:
|
||||
if volume['provider_location']:
|
||||
version = self._get_version_from_provider_location(
|
||||
volume['provider_location'])
|
||||
if not version:
|
||||
return pool
|
||||
except KeyError:
|
||||
return pool
|
||||
try:
|
||||
pool = volume_utils.extract_host(volume['host'], 'pool')
|
||||
if pool:
|
||||
LOG.debug("Pool from volume['host'] is %(pool)s.",
|
||||
{'pool': pool})
|
||||
else:
|
||||
exceptionMessage = (_(
|
||||
"Pool from volume['host'] %(host)s not found.")
|
||||
% {'host': volume['host']})
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
except Exception as ex:
|
||||
exceptionMessage = (_(
|
||||
"Pool from volume['host'] failed with: %(ex)s.")
|
||||
% {'ex': ex})
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return pool
|
||||
|
||||
def _get_version_from_provider_location(self, loc):
|
||||
"""Get the version from the provider location.
|
||||
|
||||
:param loc: the provider_location dict
|
||||
:returns: version or None
|
||||
"""
|
||||
version = None
|
||||
try:
|
||||
if isinstance(loc, six.string_types):
|
||||
name = eval(loc)
|
||||
version = name['version']
|
||||
except KeyError:
|
||||
pass
|
||||
return version
|
||||
|
||||
def manage_existing(self, volume, external_ref):
|
||||
"""Manages an existing VMAX Volume (import to Cinder).
|
||||
|
||||
|
|
|
@ -431,8 +431,7 @@ class EMCVMAXFast(object):
|
|||
storageMaskingGroupInstances = conn.Associators(
|
||||
controllerConfigService, ResultClass='CIM_DeviceMaskingGroup')
|
||||
|
||||
for storageMaskingGroupInstance in \
|
||||
storageMaskingGroupInstances:
|
||||
for storageMaskingGroupInstance in storageMaskingGroupInstances:
|
||||
|
||||
if ('_default_' in storageMaskingGroupInstance['ElementName'] and
|
||||
policyName in storageMaskingGroupInstance['ElementName']):
|
||||
|
|
|
@ -40,15 +40,17 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
|
|||
2.2.0 - Add manage/unmanage
|
||||
2.2.1 - Support for SE 8.0.3
|
||||
2.2.2 - Update Consistency Group
|
||||
2.2.3 - Pool aware scheduler(multi-pool) support
|
||||
"""
|
||||
|
||||
VERSION = "2.2.2"
|
||||
VERSION = "2.2.3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCVMAXFCDriver, self).__init__(*args, **kwargs)
|
||||
self.common = emc_vmax_common.EMCVMAXCommon(
|
||||
'FC',
|
||||
self.VERSION,
|
||||
configuration=self.configuration)
|
||||
self.zonemanager_lookup_service = fczm_utils.create_lookup_service()
|
||||
|
||||
|
|
|
@ -14,18 +14,18 @@
|
|||
# under the License.
|
||||
|
||||
import base64
|
||||
import httplib
|
||||
import os
|
||||
import socket
|
||||
import ssl
|
||||
import string
|
||||
import struct
|
||||
import urllib
|
||||
|
||||
from eventlet import patcher
|
||||
import OpenSSL
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from cinder.i18n import _, _LI
|
||||
|
||||
|
@ -74,7 +74,7 @@ def get_default_ca_certs():
|
|||
class OpenSSLConnectionDelegator(object):
|
||||
"""An OpenSSL.SSL.Connection delegator.
|
||||
|
||||
Supplies an additional 'makefile' method which httplib requires
|
||||
Supplies an additional 'makefile' method which http_client requires
|
||||
and is not present in OpenSSL.SSL.Connection.
|
||||
Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
|
||||
a delegator must be used.
|
||||
|
@ -89,7 +89,7 @@ class OpenSSLConnectionDelegator(object):
|
|||
return socket._fileobject(self.connection, *args, **kwargs)
|
||||
|
||||
|
||||
class HTTPSConnection(httplib.HTTPSConnection):
|
||||
class HTTPSConnection(http_client.HTTPSConnection):
|
||||
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
||||
strict=None, ca_certs=None, no_verification=False):
|
||||
if not pywbemAvailable:
|
||||
|
@ -101,9 +101,9 @@ class HTTPSConnection(httplib.HTTPSConnection):
|
|||
else:
|
||||
excp_lst = ()
|
||||
try:
|
||||
httplib.HTTPSConnection.__init__(self, host, port,
|
||||
key_file=key_file,
|
||||
cert_file=cert_file)
|
||||
http_client.HTTPSConnection.__init__(self, host, port,
|
||||
key_file=key_file,
|
||||
cert_file=cert_file)
|
||||
|
||||
self.key_file = None if key_file is None else key_file
|
||||
self.cert_file = None if cert_file is None else cert_file
|
||||
|
@ -255,7 +255,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None,
|
|||
"""Send request over HTTP.
|
||||
|
||||
Send XML data over HTTP to the specified url. Return the
|
||||
response in XML. Uses Python's build-in httplib. x509 may be a
|
||||
response in XML. Uses Python's build-in http_client. x509 may be a
|
||||
dictionary containing the location of the SSL certificate and key
|
||||
files.
|
||||
"""
|
||||
|
@ -274,7 +274,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None,
|
|||
localAuthHeader = None
|
||||
tryLimit = 5
|
||||
|
||||
if isinstance(data, unicode):
|
||||
if isinstance(data, six.text_type):
|
||||
data = data.encode('utf-8')
|
||||
data = '<?xml version="1.0" encoding="utf-8" ?>\n' + data
|
||||
|
||||
|
@ -309,10 +309,10 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None,
|
|||
h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin)
|
||||
|
||||
for hdr in headers:
|
||||
if isinstance(hdr, unicode):
|
||||
if isinstance(hdr, six.text_type):
|
||||
hdr = hdr.encode('utf-8')
|
||||
s = map(lambda x: string.strip(x), string.split(hdr, ":", 1))
|
||||
h.putheader(urllib.quote(s[0]), urllib.quote(s[1]))
|
||||
h.putheader(urllib.parse.quote(s[0]), urllib.parse.quote(s[1]))
|
||||
|
||||
try:
|
||||
h.endheaders()
|
||||
|
@ -328,7 +328,7 @@ def wbem_request(url, data, creds, headers=None, debug=0, x509=None,
|
|||
if response.status != 200:
|
||||
raise pywbem.cim_http.Error('HTTP error')
|
||||
|
||||
except httplib.BadStatusLine as arg:
|
||||
except http_client.BadStatusLine as arg:
|
||||
msg = (_("Bad Status line returned: %(arg)s.")
|
||||
% {'arg': arg})
|
||||
raise pywbem.cim_http.Error(msg)
|
||||
|
|
|
@ -48,15 +48,17 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
|
|||
2.2.0 - Add manage/unmanage
|
||||
2.2.1 - Support for SE 8.0.3
|
||||
2.2.2 - Update Consistency Group
|
||||
2.2.3 - Pool aware scheduler(multi-pool) support
|
||||
"""
|
||||
|
||||
VERSION = "2.2.2"
|
||||
VERSION = "2.2.3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCVMAXISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.common = (
|
||||
emc_vmax_common.EMCVMAXCommon('iSCSI',
|
||||
self.VERSION,
|
||||
configuration=self.configuration))
|
||||
|
||||
def check_for_setup_error(self):
|
||||
|
|
|
@ -623,6 +623,7 @@ class EMCVMAXMasking(object):
|
|||
:param conn: the connection to ecom
|
||||
:param storageGroupInstanceName: the storage group instance name
|
||||
:param volumeInstance: the volume instance
|
||||
:param sgName: the storage group name
|
||||
:returns: boolean
|
||||
"""
|
||||
foundStorageGroupInstanceName = (
|
||||
|
@ -901,8 +902,8 @@ class EMCVMAXMasking(object):
|
|||
conn.AssociatorNames(controllerConfigService,
|
||||
ResultClass='CIM_InitiatorMaskingGroup'))
|
||||
|
||||
for initiatorMaskingGroupInstanceName in \
|
||||
initiatorMaskingGroupInstanceNames:
|
||||
for initiatorMaskingGroupInstanceName in (
|
||||
initiatorMaskingGroupInstanceNames):
|
||||
# Check that it hasn't been deleted. If it has, break out
|
||||
# of the for loop.
|
||||
instance = self.utils.get_existing_instance(
|
||||
|
@ -919,8 +920,8 @@ class EMCVMAXMasking(object):
|
|||
# we found the existing CIM_InitiatorMaskingGroup.
|
||||
hardwareid = storageHardwareIdInstance['StorageID']
|
||||
for initiator in initiatorNames:
|
||||
if six.text_type(hardwareid).lower() == \
|
||||
six.text_type(initiator).lower():
|
||||
if six.text_type(hardwareid).lower() == (
|
||||
six.text_type(initiator).lower()):
|
||||
foundInitiatorMaskingGroupInstanceName = (
|
||||
initiatorMaskingGroupInstanceName)
|
||||
break
|
||||
|
@ -2037,10 +2038,10 @@ class EMCVMAXMasking(object):
|
|||
maskingViewDict['sgGroupName'] = defaultStorageGroupName
|
||||
maskingViewDict['volumeInstance'] = volumeInstance
|
||||
maskingViewDict['volumeName'] = volumeName
|
||||
maskingViewDict['controllerConfigService'] = \
|
||||
controllerConfigService
|
||||
maskingViewDict['storageSystemName'] = \
|
||||
storageSystemInstanceName
|
||||
maskingViewDict['controllerConfigService'] = (
|
||||
controllerConfigService)
|
||||
maskingViewDict['storageSystemName'] = (
|
||||
storageSystemInstanceName)
|
||||
sgInstanceName = self.utils.find_storage_masking_group(
|
||||
conn, controllerConfigService, defaultStorageGroupName)
|
||||
if sgInstanceName is not None:
|
||||
|
|
|
@ -64,7 +64,7 @@ class EMCVMAXProvision(object):
|
|||
theElements = [volumeInstanceName]
|
||||
|
||||
rc, job = conn.InvokeMethod(
|
||||
'ReturnElementsToStoragePool', storageConfigservice,
|
||||
'EMCReturnToStoragePool', storageConfigservice,
|
||||
TheElements=theElements)
|
||||
|
||||
if rc != 0:
|
||||
|
@ -338,13 +338,14 @@ class EMCVMAXProvision(object):
|
|||
time.time())})
|
||||
|
||||
def unbind_volume_from_storage_pool(
|
||||
self, conn, storageConfigService, volumeInstanceName,
|
||||
volumeName, extraSpecs):
|
||||
self, conn, storageConfigService, poolInstanceName,
|
||||
volumeInstanceName, volumeName, extraSpecs):
|
||||
"""Unbind a volume from a pool and return the unbound volume.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageConfigService: the storage configuration service
|
||||
instance name
|
||||
:param poolInstanceName: the pool instance name
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param volumeName: the volume name
|
||||
:param extraSpecs: additional info
|
||||
|
@ -357,6 +358,7 @@ class EMCVMAXProvision(object):
|
|||
rc, job = conn.InvokeMethod(
|
||||
'EMCUnBindElement',
|
||||
storageConfigService,
|
||||
InPool=poolInstanceName,
|
||||
TheElement=volumeInstanceName)
|
||||
|
||||
if rc != 0:
|
||||
|
@ -1079,16 +1081,14 @@ class EMCVMAXProvision(object):
|
|||
'relationName': relationName,
|
||||
'srcGroup': srcGroupInstanceName,
|
||||
'tgtGroup': tgtGroupInstanceName})
|
||||
# SyncType 8 - clone.
|
||||
# CopyState 4 - Synchronized.
|
||||
# 8 for clone.
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateGroupReplica',
|
||||
replicationService,
|
||||
RelationshipName=relationName,
|
||||
SourceGroup=srcGroupInstanceName,
|
||||
TargetGroup=tgtGroupInstanceName,
|
||||
SyncType=self.utils.get_num(8, '16'),
|
||||
WaitForCopyState=self.utils.get_num(4, '16'))
|
||||
SyncType=self.utils.get_num(8, '16'))
|
||||
|
||||
if rc != 0:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE
|
||||
from cinder.i18n import _, _LE, _LW
|
||||
from cinder.volume.drivers.emc import emc_vmax_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -29,6 +30,7 @@ POSTGROUPTYPE = 3
|
|||
EMC_ROOT = 'root/emc'
|
||||
THINPROVISIONINGCOMPOSITE = 32768
|
||||
THINPROVISIONING = 5
|
||||
INFO_SRC_V3 = 3
|
||||
|
||||
|
||||
class EMCVMAXProvisionV3(object):
|
||||
|
@ -411,12 +413,9 @@ class EMCVMAXProvisionV3(object):
|
|||
:param slo: slo string e.g Bronze
|
||||
:param workload: workload string e.g DSS
|
||||
:param extraSpecs: additional info
|
||||
:returns: maximumVolumeSize - the maximum volume size supported
|
||||
:returns: minimumVolumeSize - the minimum volume size supported
|
||||
:returns: supportedSizeDict
|
||||
"""
|
||||
maximumVolumeSize = None
|
||||
minimumVolumeSize = None
|
||||
|
||||
supportedSizeDict = {}
|
||||
storagePoolCapabilityInstanceName = self._get_storage_pool_capability(
|
||||
conn, poolInstanceName)
|
||||
if storagePoolCapabilityInstanceName:
|
||||
|
@ -426,11 +425,7 @@ class EMCVMAXProvisionV3(object):
|
|||
supportedSizeDict = self._get_supported_size_range_for_SLO(
|
||||
conn, storageConfigService, poolInstanceName,
|
||||
storagePoolSettingInstanceName, extraSpecs)
|
||||
|
||||
maximumVolumeSize = supportedSizeDict['MaximumVolumeSize']
|
||||
minimumVolumeSize = supportedSizeDict['MinimumVolumeSize']
|
||||
|
||||
return maximumVolumeSize, minimumVolumeSize
|
||||
return supportedSizeDict
|
||||
|
||||
def activate_snap_relationship(
|
||||
self, conn, repServiceInstanceName, syncInstanceName, extraSpecs):
|
||||
|
@ -578,3 +573,97 @@ class EMCVMAXProvisionV3(object):
|
|||
LOG.error(exceptionMsg)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMsg)
|
||||
return rc, job
|
||||
|
||||
def get_srp_pool_stats(self, conn, arrayInfo):
|
||||
"""Get the totalManagedSpace, remainingManagedSpace.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param arrayInfo: the array dict
|
||||
:returns: totalCapacityGb
|
||||
:returns: remainingCapacityGb
|
||||
"""
|
||||
totalCapacityGb = -1
|
||||
remainingCapacityGb = -1
|
||||
storageSystemInstanceName = self.utils.find_storageSystem(
|
||||
conn, arrayInfo['SerialNumber'])
|
||||
|
||||
srpPoolInstanceNames = conn.AssociatorNames(
|
||||
storageSystemInstanceName,
|
||||
ResultClass='Symm_SRPStoragePool')
|
||||
|
||||
for srpPoolInstanceName in srpPoolInstanceNames:
|
||||
poolnameStr = self.utils.get_pool_name(conn, srpPoolInstanceName)
|
||||
|
||||
if six.text_type(arrayInfo['PoolName']) == (
|
||||
six.text_type(poolnameStr)):
|
||||
try:
|
||||
# Check that pool hasn't suddently been deleted.
|
||||
srpPoolInstance = conn.GetInstance(srpPoolInstanceName)
|
||||
propertiesList = srpPoolInstance.properties.items()
|
||||
for properties in propertiesList:
|
||||
if properties[0] == 'TotalManagedSpace':
|
||||
cimProperties = properties[1]
|
||||
totalManagedSpace = cimProperties.value
|
||||
totalCapacityGb = self.utils.convert_bits_to_gbs(
|
||||
totalManagedSpace)
|
||||
elif properties[0] == 'RemainingManagedSpace':
|
||||
cimProperties = properties[1]
|
||||
remainingManagedSpace = cimProperties.value
|
||||
remainingCapacityGb = (
|
||||
self.utils.convert_bits_to_gbs(
|
||||
remainingManagedSpace))
|
||||
except Exception:
|
||||
pass
|
||||
remainingSLOCapacityGb = (
|
||||
self._get_remaining_slo_capacity_wlp(
|
||||
conn, srpPoolInstanceName, arrayInfo,
|
||||
storageSystemInstanceName['Name']))
|
||||
if remainingSLOCapacityGb != -1:
|
||||
remainingCapacityGb = remainingSLOCapacityGb
|
||||
else:
|
||||
LOG.warning(_LW(
|
||||
"Remaining capacity %(remainingCapacityGb)s "
|
||||
"GBs is determined from SRP pool capacity "
|
||||
"and not the SLO capacity. Performance may "
|
||||
"not be what you expect."),
|
||||
{'remainingCapacityGb': remainingCapacityGb})
|
||||
|
||||
return totalCapacityGb, remainingCapacityGb
|
||||
|
||||
def _get_remaining_slo_capacity_wlp(self, conn, srpPoolInstanceName,
|
||||
arrayInfo, systemName):
|
||||
"""Get the remaining SLO capacity.
|
||||
|
||||
This is derived from the WLP portion of Unisphere. Please
|
||||
see the SMIProvider doc and the readme doc for details.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param srpPoolInstanceName: SRP instance name
|
||||
:param arrayInfo: the array dict
|
||||
:param systemName: the system name
|
||||
:returns: remainingCapacityGb
|
||||
"""
|
||||
remainingCapacityGb = -1
|
||||
storageConfigService = (
|
||||
self.utils.find_storage_configuration_service(
|
||||
conn, systemName))
|
||||
|
||||
supportedSizeDict = (
|
||||
self.get_volume_range(
|
||||
conn, storageConfigService, srpPoolInstanceName,
|
||||
arrayInfo['SLO'], arrayInfo['Workload'],
|
||||
None))
|
||||
try:
|
||||
# Information source is V3.
|
||||
if supportedSizeDict['EMCInformationSource'] == INFO_SRC_V3:
|
||||
remainingCapacityGb = self.utils.convert_bits_to_gbs(
|
||||
supportedSizeDict['EMCRemainingSLOCapacity'])
|
||||
LOG.debug("Received remaining SLO Capacity "
|
||||
"%(remainingCapacityGb)s GBs for SLO "
|
||||
"%(SLO)s and workload %(workload)s.",
|
||||
{'remainingCapacityGb': remainingCapacityGb,
|
||||
'SLO': arrayInfo['SLO'],
|
||||
'workload': arrayInfo['Workload']})
|
||||
except KeyError:
|
||||
pass
|
||||
return remainingCapacityGb
|
||||
|
|
|
@ -500,6 +500,7 @@ class EMCVMAXUtils(object):
|
|||
|
||||
:param conn: connection to the ecom server
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param sgName: the storage group name
|
||||
:returns: foundStorageGroupInstanceName
|
||||
"""
|
||||
foundStorageGroupInstanceName = None
|
||||
|
@ -568,8 +569,7 @@ class EMCVMAXUtils(object):
|
|||
conn.Associators(controllerConfigService,
|
||||
ResultClass='CIM_DeviceMaskingGroup'))
|
||||
|
||||
for storageMaskingGroupInstance in \
|
||||
storageMaskingGroupInstances:
|
||||
for storageMaskingGroupInstance in storageMaskingGroupInstances:
|
||||
|
||||
if storageGroupName == storageMaskingGroupInstance['ElementName']:
|
||||
# Check that it has not been deleted recently.
|
||||
|
@ -654,242 +654,6 @@ class EMCVMAXUtils(object):
|
|||
|
||||
return instanceName
|
||||
|
||||
def get_ecom_server(self, filename):
|
||||
"""Given the file name get the ecomPort and ecomIP from it.
|
||||
|
||||
:param filename: the path and file name of the emc configuration file
|
||||
:returns: ecomIp - the ecom IP address
|
||||
:returns: ecomPort - the ecom port
|
||||
"""
|
||||
ecomIp = self._parse_from_file(filename, 'EcomServerIp')
|
||||
ecomPort = self._parse_from_file(filename, 'EcomServerPort')
|
||||
if ecomIp is not None and ecomPort is not None:
|
||||
LOG.debug("Ecom IP: %(ecomIp)s Port: %(ecomPort)s.",
|
||||
{'ecomIp': ecomIp, 'ecomPort': ecomPort})
|
||||
return ecomIp, ecomPort
|
||||
else:
|
||||
LOG.debug("Ecom server not found.")
|
||||
return None
|
||||
|
||||
def get_ecom_cred(self, filename):
|
||||
"""Given the filename get the ecomUser and ecomPasswd.
|
||||
|
||||
:param filename: the path and filename of the emc configuration file
|
||||
:returns: ecomUser - the ecom user
|
||||
:returns: ecomPasswd - the ecom password
|
||||
"""
|
||||
ecomUser = self._parse_from_file(filename, 'EcomUserName')
|
||||
ecomPasswd = self._parse_from_file(filename, 'EcomPassword')
|
||||
if ecomUser is not None and ecomPasswd is not None:
|
||||
return ecomUser, ecomPasswd
|
||||
else:
|
||||
LOG.debug("Ecom user not found.")
|
||||
return None
|
||||
|
||||
def get_ecom_cred_SSL(self, filename):
|
||||
"""Given the filename get the ecomUser and ecomPasswd.
|
||||
|
||||
:param filename: the path and filename of the emc configuration file
|
||||
:returns: string -- ecomUseSSL
|
||||
:returns: string -- ecomCACert
|
||||
:returns: string -- ecomNoVerification
|
||||
"""
|
||||
ecomUseSSL = self._parse_from_file(filename, 'EcomUseSSL')
|
||||
ecomCACert = self._parse_from_file(filename, 'EcomCACert')
|
||||
ecomNoVerification = self._parse_from_file(
|
||||
filename, 'EcomNoVerification')
|
||||
if ecomUseSSL is not None and ecomUseSSL == 'True':
|
||||
ecomUseSSL = True
|
||||
if ecomNoVerification is not None and ecomNoVerification == 'True':
|
||||
ecomNoVerification = True
|
||||
return ecomUseSSL, ecomCACert, ecomNoVerification
|
||||
else:
|
||||
ecomUseSSL = False
|
||||
ecomNoVerification = False
|
||||
return ecomUseSSL, ecomCACert, ecomNoVerification
|
||||
|
||||
def parse_file_to_get_port_group_name(self, fileName):
|
||||
"""Parses a file and chooses a port group randomly.
|
||||
|
||||
Given a file, parse it to get all the possible
|
||||
portGroupElements and choose one randomly.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: string -- portGroupName - the name of the port group chosen
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
portGroupName = None
|
||||
myFile = open(fileName, 'r')
|
||||
data = myFile.read()
|
||||
myFile.close()
|
||||
dom = minidom.parseString(data)
|
||||
portGroupElements = dom.getElementsByTagName('PortGroup')
|
||||
|
||||
if portGroupElements is not None and len(portGroupElements) > 0:
|
||||
portGroupNames = []
|
||||
for portGroupElement in portGroupElements:
|
||||
if portGroupElement.hasChildNodes():
|
||||
portGroupName = portGroupElement.childNodes[0].nodeValue
|
||||
portGroupName = portGroupName.replace('\n', '')
|
||||
portGroupName = portGroupName.replace('\r', '')
|
||||
portGroupName = portGroupName.replace('\t', '')
|
||||
portGroupName = portGroupName.strip()
|
||||
if portGroupName:
|
||||
portGroupNames.append(portGroupName)
|
||||
|
||||
LOG.debug("portGroupNames: %(portGroupNames)s.",
|
||||
{'portGroupNames': portGroupNames})
|
||||
numPortGroups = len(portGroupNames)
|
||||
if numPortGroups > 0:
|
||||
selectedPortGroupName = (
|
||||
portGroupNames[random.randint(0, numPortGroups - 1)])
|
||||
LOG.debug("Returning Selected Port Group: "
|
||||
"%(selectedPortGroupName)s.",
|
||||
{'selectedPortGroupName': selectedPortGroupName})
|
||||
return selectedPortGroupName
|
||||
|
||||
# If reaches here without returning yet, raise exception.
|
||||
exception_message = (_("No Port Group elements found in config file."))
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
def _parse_from_file(self, fileName, stringToParse):
|
||||
"""Parse the string from XML.
|
||||
|
||||
Remove newlines, tabs and trailing spaces.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:param stringToParse: the name of the tag to get the value for
|
||||
:returns: string -- the returned string; value of the tag
|
||||
"""
|
||||
retString = None
|
||||
myFile = open(fileName, 'r')
|
||||
data = myFile.read()
|
||||
myFile.close()
|
||||
dom = minidom.parseString(data)
|
||||
tag = dom.getElementsByTagName(stringToParse)
|
||||
if tag is not None and len(tag) > 0:
|
||||
strXml = tag[0].toxml()
|
||||
strXml = strXml.replace('<%s>' % stringToParse, '')
|
||||
strXml = strXml.replace('\n', '')
|
||||
strXml = strXml.replace('\r', '')
|
||||
strXml = strXml.replace('\t', '')
|
||||
retString = strXml.replace('</%s>' % stringToParse, '')
|
||||
retString = retString.strip()
|
||||
return retString
|
||||
|
||||
def parse_fast_policy_name_from_file(self, fileName):
|
||||
"""Parse the fast policy name from config file.
|
||||
|
||||
If it is not there, then NON FAST is assumed.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: fastPolicyName - the fast policy name
|
||||
"""
|
||||
|
||||
fastPolicyName = self._parse_from_file(fileName, 'FastPolicy')
|
||||
if fastPolicyName:
|
||||
LOG.debug("File %(fileName)s: Fast Policy is %(fastPolicyName)s.",
|
||||
{'fileName': fileName,
|
||||
'fastPolicyName': fastPolicyName})
|
||||
return fastPolicyName
|
||||
else:
|
||||
LOG.info(_LI("Fast Policy not found."))
|
||||
return None
|
||||
|
||||
def parse_array_name_from_file(self, fileName):
|
||||
"""Parse the array name from config file.
|
||||
|
||||
If it is not there then there should only be one array configured to
|
||||
the ecom. If there is more than one then erroneous results can occur.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: string -- arrayName - the array name
|
||||
"""
|
||||
arrayName = self._parse_from_file(fileName, 'Array')
|
||||
if arrayName:
|
||||
return arrayName
|
||||
else:
|
||||
LOG.debug("Array not found from config file.")
|
||||
return None
|
||||
|
||||
def parse_pool_name_from_file(self, fileName):
|
||||
"""Parse the pool name from config file.
|
||||
|
||||
If it is not there then we will attempt to get it from extra specs.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: string -- poolName - the pool name
|
||||
"""
|
||||
poolName = self._parse_from_file(fileName, 'Pool')
|
||||
if poolName:
|
||||
return poolName
|
||||
else:
|
||||
LOG.debug("Pool not found from config file.")
|
||||
return None
|
||||
|
||||
def parse_slo_from_file(self, fileName):
|
||||
"""Parse the slo from config file.
|
||||
|
||||
Please note that the string 'NONE' is returned if it is not found.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: string -- the slo or 'NONE'
|
||||
"""
|
||||
slo = self._parse_from_file(fileName, 'SLO')
|
||||
if slo:
|
||||
return slo
|
||||
else:
|
||||
LOG.debug("SLO not in config file. "
|
||||
"Defaulting to NONE.")
|
||||
return 'NONE'
|
||||
|
||||
def parse_workload_from_file(self, fileName):
|
||||
"""Parse the workload from config file.
|
||||
|
||||
Please note that the string 'NONE' is returned if it is not found.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: string -- the workload or 'NONE'
|
||||
"""
|
||||
workload = self._parse_from_file(fileName, 'Workload')
|
||||
if workload:
|
||||
return workload
|
||||
else:
|
||||
LOG.debug("Workload not in config file. "
|
||||
"Defaulting to NONE.")
|
||||
return 'NONE'
|
||||
|
||||
def parse_interval_from_file(self, fileName):
|
||||
"""Parse the interval from config file.
|
||||
|
||||
If it is not there then the default will be used.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: interval - the interval in seconds
|
||||
"""
|
||||
interval = self._parse_from_file(fileName, 'Interval')
|
||||
if interval:
|
||||
return interval
|
||||
else:
|
||||
LOG.debug("Interval not overridden, default of 10 assumed.")
|
||||
return None
|
||||
|
||||
def parse_retries_from_file(self, fileName):
|
||||
"""Parse the retries from config file.
|
||||
|
||||
If it is not there then the default will be used.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: retries - the max number of retries
|
||||
"""
|
||||
retries = self._parse_from_file(fileName, 'Retries')
|
||||
if retries:
|
||||
return retries
|
||||
else:
|
||||
LOG.debug("Retries not overridden, default of 60 assumed.")
|
||||
return None
|
||||
|
||||
def parse_pool_instance_id(self, poolInstanceId):
|
||||
"""Given the instance Id parse the pool name and system name from it.
|
||||
|
||||
|
@ -1223,14 +987,13 @@ class EMCVMAXUtils(object):
|
|||
LOG.debug(
|
||||
"storagePoolName: %(poolName)s, storageSystemName: %(array)s.",
|
||||
{'poolName': storagePoolName, 'array': storageSystemName})
|
||||
storageSystemInstanceName = self.find_storageSystem(conn,
|
||||
storageSystemName)
|
||||
poolInstanceNames = conn.AssociatorNames(
|
||||
storageSystemInstanceName,
|
||||
ResultClass='EMC_VirtualProvisioningPool')
|
||||
poolInstanceNames = conn.EnumerateInstanceNames(
|
||||
'EMC_VirtualProvisioningPool')
|
||||
for poolInstanceName in poolInstanceNames:
|
||||
poolName = self._get_pool_name(conn, poolInstanceName)
|
||||
if (poolName == storagePoolName):
|
||||
poolName, systemName = (
|
||||
self.parse_pool_instance_id(poolInstanceName['InstanceID']))
|
||||
if (poolName == storagePoolName and
|
||||
storageSystemName in systemName):
|
||||
# Check that the pool hasn't been recently deleted.
|
||||
instance = self.get_existing_instance(conn, poolInstanceName)
|
||||
if instance is None:
|
||||
|
@ -1622,13 +1385,27 @@ class EMCVMAXUtils(object):
|
|||
:returns: foundPoolInstanceName
|
||||
:returns: string -- systemNameStr
|
||||
"""
|
||||
foundPoolInstanceName = None
|
||||
vpoolInstanceNames = conn.AssociatorNames(
|
||||
storageSystemInstanceName,
|
||||
ResultClass='EMC_VirtualProvisioningPool')
|
||||
|
||||
return self._get_pool_instance_and_system_name(
|
||||
conn, vpoolInstanceNames, storageSystemInstanceName,
|
||||
poolNameInStr)
|
||||
for vpoolInstanceName in vpoolInstanceNames:
|
||||
poolInstanceId = vpoolInstanceName['InstanceID']
|
||||
# Example: SYMMETRIX+000195900551+TP+Sol_Innov
|
||||
poolnameStr, systemNameStr = self.parse_pool_instance_id(
|
||||
poolInstanceId)
|
||||
if poolnameStr is not None and systemNameStr is not None:
|
||||
if six.text_type(poolNameInStr) == six.text_type(poolnameStr):
|
||||
# check that the pool hasn't recently been deleted.
|
||||
try:
|
||||
conn.GetInstance(vpoolInstanceName)
|
||||
foundPoolInstanceName = vpoolInstanceName
|
||||
except Exception:
|
||||
foundPoolInstanceName = None
|
||||
break
|
||||
|
||||
return foundPoolInstanceName, systemNameStr
|
||||
|
||||
def get_pool_and_system_name_v3(
|
||||
self, conn, storageSystemInstanceName, poolNameInStr):
|
||||
|
@ -1640,41 +1417,29 @@ class EMCVMAXUtils(object):
|
|||
:returns: foundPoolInstanceName
|
||||
:returns: string -- systemNameStr
|
||||
"""
|
||||
foundPoolInstanceName = None
|
||||
srpPoolInstanceNames = conn.AssociatorNames(
|
||||
storageSystemInstanceName,
|
||||
ResultClass='Symm_SRPStoragePool')
|
||||
|
||||
return self._get_pool_instance_and_system_name(
|
||||
conn, srpPoolInstanceNames, storageSystemInstanceName,
|
||||
poolNameInStr)
|
||||
|
||||
def _get_pool_instance_and_system_name(
|
||||
self, conn, poolInstanceNames, storageSystemInstanceName,
|
||||
poolname):
|
||||
"""Get the pool instance and the system name
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param poolInstanceNames: list of pool instances
|
||||
:param storageSystemInstanceName: storage system instance name
|
||||
:param poolname: pool name (string)
|
||||
:returns: foundPoolInstanceName, systemNameStr
|
||||
"""
|
||||
foundPoolInstanceName = None
|
||||
poolnameStr = None
|
||||
systemNameStr = storageSystemInstanceName['Name']
|
||||
for poolInstanceName in poolInstanceNames:
|
||||
for srpPoolInstanceName in srpPoolInstanceNames:
|
||||
poolInstanceID = srpPoolInstanceName['InstanceID']
|
||||
# Example: SYMMETRIX-+-000196700535-+-SR-+-SRP_1
|
||||
# Example: SYMMETRIX+000195900551+TP+Sol_Innov
|
||||
poolnameStr = self._get_pool_name(conn, poolInstanceName)
|
||||
if poolnameStr is not None:
|
||||
if six.text_type(poolname) == six.text_type(poolnameStr):
|
||||
foundPoolInstanceName = poolInstanceName
|
||||
poolnameStr, systemNameStr = self.parse_pool_instance_id_v3(
|
||||
poolInstanceID)
|
||||
if poolnameStr is not None and systemNameStr is not None:
|
||||
if six.text_type(poolNameInStr) == six.text_type(poolnameStr):
|
||||
try:
|
||||
conn.GetInstance(srpPoolInstanceName)
|
||||
foundPoolInstanceName = srpPoolInstanceName
|
||||
except Exception:
|
||||
foundPoolInstanceName = None
|
||||
break
|
||||
|
||||
return foundPoolInstanceName, systemNameStr
|
||||
|
||||
def _get_pool_name(self, conn, poolInstanceName):
|
||||
"""The pool name from the instance
|
||||
def get_pool_name(self, conn, poolInstanceName):
|
||||
"""Get the pool name from the instance
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param poolInstanceName: the pool instance
|
||||
|
@ -1689,7 +1454,7 @@ class EMCVMAXUtils(object):
|
|||
return poolnameStr
|
||||
|
||||
def find_storageSystem(self, conn, arrayStr):
|
||||
"""Find an array instance name given the array name.
|
||||
"""Find an array instance name by the array name.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param arrayStr: the array Serial number (string)
|
||||
|
@ -1844,28 +1609,21 @@ class EMCVMAXUtils(object):
|
|||
LOG.debug("metaMembers: %(members)s.", {'members': metaMembers})
|
||||
return metaMembers
|
||||
|
||||
def get_meta_members_capacity_in_byte(self, conn, volumeInstanceNames):
|
||||
"""Get the capacity in byte of all meta device member volumes.
|
||||
def get_meta_members_capacity_in_bit(self, conn, volumeInstanceNames):
|
||||
"""Get the capacity in bits of all meta device member volumes.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param volumeInstanceNames: array contains meta device member volumes
|
||||
:returns: array contains capacities of each member device in bits
|
||||
"""
|
||||
capacitiesInByte = []
|
||||
headVolume = conn.GetInstance(volumeInstanceNames[0])
|
||||
totalSizeInByte = (
|
||||
headVolume['ConsumableBlocks'] * headVolume['BlockSize'])
|
||||
volumeInstanceNames.pop(0)
|
||||
capacitiesInBit = []
|
||||
for volumeInstanceName in volumeInstanceNames:
|
||||
volumeInstance = conn.GetInstance(volumeInstanceName)
|
||||
numOfBlocks = volumeInstance['ConsumableBlocks']
|
||||
blockSize = volumeInstance['BlockSize']
|
||||
volumeSizeInByte = numOfBlocks * blockSize
|
||||
capacitiesInByte.append(volumeSizeInByte)
|
||||
totalSizeInByte = totalSizeInByte - volumeSizeInByte
|
||||
|
||||
capacitiesInByte.insert(0, totalSizeInByte)
|
||||
return capacitiesInByte
|
||||
volumeSizeInbits = numOfBlocks * blockSize
|
||||
capacitiesInBit.append(volumeSizeInbits)
|
||||
return capacitiesInBit
|
||||
|
||||
def get_existing_instance(self, conn, instanceName):
|
||||
"""Check that the instance name still exists and return the instance.
|
||||
|
@ -2002,6 +1760,373 @@ class EMCVMAXUtils(object):
|
|||
LOG.warning(_LW("Cannot determine the hardware type."))
|
||||
return hardwareTypeId
|
||||
|
||||
def _process_tag(self, element, tagName):
|
||||
"""Process the tag to get the value.
|
||||
|
||||
:param element: the parent element
|
||||
:param tagName: the tag name
|
||||
:returns: nodeValue(can be None)
|
||||
"""
|
||||
nodeValue = None
|
||||
try:
|
||||
processedElement = element.getElementsByTagName(tagName)[0]
|
||||
nodeValue = processedElement.childNodes[0].nodeValue
|
||||
if nodeValue:
|
||||
nodeValue = nodeValue.strip()
|
||||
except IndexError:
|
||||
pass
|
||||
return nodeValue
|
||||
|
||||
def _get_connection_info(self, ecomElement):
|
||||
"""Given the filename get the ecomUser and ecomPasswd.
|
||||
|
||||
:param ecomElement: the ecom element
|
||||
:returns: dict -- connargs - the connection info dictionary
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
connargs = {}
|
||||
connargs['EcomServerIp'] = (
|
||||
self._process_tag(ecomElement, 'EcomServerIp'))
|
||||
connargs['EcomServerPort'] = (
|
||||
self._process_tag(ecomElement, 'EcomServerPort'))
|
||||
connargs['EcomUserName'] = (
|
||||
self._process_tag(ecomElement, 'EcomUserName'))
|
||||
connargs['EcomPassword'] = (
|
||||
self._process_tag(ecomElement, 'EcomPassword'))
|
||||
|
||||
for k, __ in connargs.items():
|
||||
if connargs[k] is None:
|
||||
exceptionMessage = (_(
|
||||
"EcomServerIp, EcomServerPort, EcomUserName, "
|
||||
"EcomPassword must have valid values."))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
# These can be None
|
||||
connargs['EcomUseSSL'] = self._process_tag(ecomElement, 'EcomUseSSL')
|
||||
connargs['EcomCACert'] = self._process_tag(ecomElement, 'EcomCACert')
|
||||
connargs['EcomNoVerification'] = (
|
||||
self._process_tag(ecomElement, 'EcomNoVerification'))
|
||||
|
||||
if connargs['EcomUseSSL'] and connargs['EcomUseSSL'] == 'True':
|
||||
connargs['EcomUseSSL'] = True
|
||||
if connargs['EcomNoVerification'] and (
|
||||
connargs['EcomNoVerification'] == 'True'):
|
||||
connargs['EcomNoVerification'] = True
|
||||
else:
|
||||
connargs['EcomUseSSL'] = False
|
||||
connargs['EcomNoVerification'] = False
|
||||
|
||||
return connargs
|
||||
|
||||
def _fill_record(self, connargs, serialNumber, poolName,
|
||||
portGroup, element):
|
||||
"""Fill a single record.
|
||||
|
||||
:param connargs: the connection info
|
||||
:param serialNumber: the serial number of array
|
||||
:param poolName: the poolname
|
||||
:param portGroup: the portGroup
|
||||
:param element: the parent element
|
||||
:returns: dict -- kwargs
|
||||
"""
|
||||
kwargs = {}
|
||||
kwargs['EcomServerIp'] = connargs['EcomServerIp']
|
||||
kwargs['EcomServerPort'] = connargs['EcomServerPort']
|
||||
kwargs['EcomUserName'] = connargs['EcomUserName']
|
||||
kwargs['EcomPassword'] = connargs['EcomPassword']
|
||||
kwargs['EcomUseSSL'] = connargs['EcomUseSSL']
|
||||
kwargs['EcomCACert'] = connargs['EcomCACert']
|
||||
kwargs['EcomNoVerification'] = connargs['EcomNoVerification']
|
||||
|
||||
slo = self._process_tag(element, 'SLO')
|
||||
if slo is None:
|
||||
slo = 'NONE'
|
||||
kwargs['SLO'] = slo
|
||||
workload = self._process_tag(element, 'Workload')
|
||||
if workload is None:
|
||||
workload = 'NONE'
|
||||
kwargs['Workload'] = workload
|
||||
fastPolicy = self._process_tag(element, 'FastPolicy')
|
||||
kwargs['FastPolicy'] = fastPolicy
|
||||
kwargs['SerialNumber'] = serialNumber
|
||||
kwargs['PoolName'] = poolName
|
||||
kwargs['PortGroup'] = portGroup
|
||||
|
||||
return kwargs
|
||||
|
||||
def _multi_pool_support(self, fileName):
|
||||
"""Multi pool support.
|
||||
|
||||
<EMC>
|
||||
<EcomServers>
|
||||
<EcomServer>
|
||||
<EcomServerIp>10.108.246.202</EcomServerIp>
|
||||
...
|
||||
<Arrays>
|
||||
<Array>
|
||||
<SerialNumber>000198700439</SerialNumber>
|
||||
...
|
||||
<Pools>
|
||||
<Pool>
|
||||
<PoolName>FC_SLVR1</PoolName>
|
||||
...
|
||||
</Pool>
|
||||
</Pools>
|
||||
</Array>
|
||||
</Arrays>
|
||||
</EcomServer>
|
||||
</EcomServers>
|
||||
</EMC>
|
||||
|
||||
:param fileName: the configuration file
|
||||
:returns: list
|
||||
"""
|
||||
myList = []
|
||||
connargs = {}
|
||||
myFile = open(fileName, 'r')
|
||||
data = myFile.read()
|
||||
myFile.close()
|
||||
dom = minidom.parseString(data)
|
||||
interval = self._process_tag(dom, 'Interval')
|
||||
retries = self._process_tag(dom, 'Retries')
|
||||
try:
|
||||
ecomElements = dom.getElementsByTagName('EcomServer')
|
||||
if ecomElements and len(ecomElements) > 0:
|
||||
for ecomElement in ecomElements:
|
||||
connargs = self._get_connection_info(ecomElement)
|
||||
arrayElements = ecomElement.getElementsByTagName('Array')
|
||||
if arrayElements and len(arrayElements) > 0:
|
||||
for arrayElement in arrayElements:
|
||||
myList = self._get_pool_info(arrayElement,
|
||||
fileName, connargs,
|
||||
interval, retries,
|
||||
myList)
|
||||
else:
|
||||
LOG.error(_LE(
|
||||
"Please check your xml for format or syntax "
|
||||
"errors. Please see documentation for more "
|
||||
"details."))
|
||||
except IndexError:
|
||||
pass
|
||||
return myList
|
||||
|
||||
def _single_pool_support(self, fileName):
|
||||
"""Single pool support.
|
||||
|
||||
<EMC>
|
||||
<EcomServerIp>10.108.246.202</EcomServerIp>
|
||||
<EcomServerPort>5988</EcomServerPort>
|
||||
<EcomUserName>admin</EcomUserName>
|
||||
<EcomPassword>#1Password</EcomPassword>
|
||||
<PortGroups>
|
||||
<PortGroup>OS-PORTGROUP1-PG</PortGroup>
|
||||
</PortGroups>
|
||||
<Array>000198700439</Array>
|
||||
<Pool>FC_SLVR1</Pool>
|
||||
</EMC>
|
||||
|
||||
:param fileName: the configuration file
|
||||
:returns: list
|
||||
"""
|
||||
myList = []
|
||||
kwargs = {}
|
||||
connargs = {}
|
||||
myFile = open(fileName, 'r')
|
||||
data = myFile.read()
|
||||
myFile.close()
|
||||
dom = minidom.parseString(data)
|
||||
try:
|
||||
connargs = self._get_connection_info(dom)
|
||||
interval = self._process_tag(dom, 'Interval')
|
||||
retries = self._process_tag(dom, 'Retries')
|
||||
portGroup = self._get_random_portgroup(dom)
|
||||
|
||||
serialNumber = self._process_tag(dom, 'Array')
|
||||
if serialNumber is None:
|
||||
LOG.error(_LE(
|
||||
"Array Serial Number must be in the file "
|
||||
"%(fileName)s."),
|
||||
{'fileName': fileName})
|
||||
poolName = self._process_tag(dom, 'Pool')
|
||||
if poolName is None:
|
||||
LOG.error(_LE(
|
||||
"PoolName must be in the file "
|
||||
"%(fileName)s."),
|
||||
{'fileName': fileName})
|
||||
kwargs = self._fill_record(
|
||||
connargs, serialNumber, poolName, portGroup, dom)
|
||||
if interval:
|
||||
kwargs['Interval'] = interval
|
||||
if retries:
|
||||
kwargs['Retries'] = retries
|
||||
|
||||
myList.append(kwargs)
|
||||
except IndexError:
|
||||
pass
|
||||
return myList
|
||||
|
||||
def parse_file_to_get_array_map(self, fileName):
|
||||
"""Parses a file and gets array map.
|
||||
|
||||
Given a file, parse it to get array and any pool(s) or
|
||||
fast policy(s), SLOs, Workloads that might exist.
|
||||
|
||||
:param fileName: the path and name of the file
|
||||
:returns: list
|
||||
"""
|
||||
# Multi-pool support.
|
||||
myList = self._multi_pool_support(fileName)
|
||||
if len(myList) == 0:
|
||||
myList = self._single_pool_support(fileName)
|
||||
|
||||
return myList
|
||||
|
||||
def extract_record(self, arrayInfo, pool):
|
||||
"""Given pool string determine the correct record.
|
||||
|
||||
The poolName and the serialNumber will determine the
|
||||
correct record to return in VMAX2.
|
||||
The poolName, SLO and the serialNumber will determine the
|
||||
correct record to return in VMAX3.
|
||||
|
||||
:param arrayInfo: list of records
|
||||
:param pool: e.g 'SATA_BRONZE1+000198700439'
|
||||
'SRP_1+Bronze+000198700555'
|
||||
:returns: single record
|
||||
"""
|
||||
foundArrayInfoRec = {}
|
||||
if pool:
|
||||
for arrayInfoRec in arrayInfo:
|
||||
if pool.count('+') == 2:
|
||||
compString = ("%(slo)s+%(poolName)s+%(array)s"
|
||||
% {'slo': arrayInfoRec['SLO'],
|
||||
'poolName': arrayInfoRec['PoolName'],
|
||||
'array': arrayInfoRec['SerialNumber']})
|
||||
else:
|
||||
compString = ("%(poolName)s+%(array)s"
|
||||
% {'poolName': arrayInfoRec['PoolName'],
|
||||
'array': arrayInfoRec['SerialNumber']})
|
||||
if compString == pool:
|
||||
LOG.info(_LI(
|
||||
"The pool_name from extraSpecs is %(pool)s."),
|
||||
{'pool': pool})
|
||||
foundArrayInfoRec = arrayInfoRec
|
||||
break
|
||||
else:
|
||||
foundArrayInfoRec = self._get_serial_number(arrayInfo)
|
||||
|
||||
return foundArrayInfoRec
|
||||
|
||||
def _get_random_portgroup(self, element):
|
||||
"""Get a portgroup from list of portgroup.
|
||||
|
||||
Parse all available port groups under a particular
|
||||
array and choose one.
|
||||
|
||||
:param element: the parent element
|
||||
:returns: the randomly chosen port group
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
portGroupElements = element.getElementsByTagName('PortGroup')
|
||||
if portGroupElements and len(portGroupElements) > 0:
|
||||
portGroupNames = []
|
||||
for __ in portGroupElements:
|
||||
portGroupName = self._process_tag(
|
||||
element, 'PortGroup')
|
||||
if portGroupName:
|
||||
portGroupNames.append(portGroupName)
|
||||
|
||||
LOG.debug("portGroupNames: %(portGroupNames)s.",
|
||||
{'portGroupNames': portGroupNames})
|
||||
numPortGroups = len(portGroupNames)
|
||||
if numPortGroups > 0:
|
||||
selectedPortGroupName = (
|
||||
portGroupNames[random.randint(0, numPortGroups - 1)])
|
||||
LOG.debug("Returning selected PortGroup: "
|
||||
"%(selectedPortGroupName)s.",
|
||||
{'selectedPortGroupName': selectedPortGroupName})
|
||||
return selectedPortGroupName
|
||||
|
||||
exception_message = (_("No PortGroup elements found in config file."))
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
def _get_serial_number(self, arrayInfo):
|
||||
"""If we don't have a pool then we just get the serial number.
|
||||
|
||||
If there is more then one serial number we must return an
|
||||
error and a recommendation to edit the EMC conf file.
|
||||
|
||||
:param arrayInfo: list of records
|
||||
:returns: any record where serial number exists
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
serialNumberList = []
|
||||
foundRecord = {}
|
||||
|
||||
for arrayInfoRec in arrayInfo:
|
||||
serialNumberList.append(arrayInfoRec['SerialNumber'])
|
||||
foundRecord = arrayInfoRec
|
||||
|
||||
if len(set(serialNumberList)) > 1:
|
||||
# We have more than one serial number in the dict.
|
||||
exception_message = (_("Multiple SerialNumbers found, when only "
|
||||
"one was expected for this operation. "
|
||||
"Please change your EMC config file."))
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
return foundRecord
|
||||
|
||||
def _get_pool_info(self, arrayElement, fileName, connargs, interval,
|
||||
retries, myList):
|
||||
"""Get pool information from element.
|
||||
|
||||
:param arrayElement: arrayElement
|
||||
:param fileName: configuration file
|
||||
:param connargs: connection arguments
|
||||
:param interval: interval, can be None
|
||||
:param retries: retries, can be None
|
||||
:param myList: list (input)
|
||||
:returns: list (output)
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
kwargs = {}
|
||||
portGroup = self._get_random_portgroup(arrayElement)
|
||||
serialNumber = self._process_tag(
|
||||
arrayElement, 'SerialNumber')
|
||||
if serialNumber is None:
|
||||
exceptionMessage = (_(
|
||||
"SerialNumber must be in the file "
|
||||
"%(fileName)s."),
|
||||
{'fileName': fileName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
poolElements = arrayElement.getElementsByTagName('Pool')
|
||||
if poolElements and len(poolElements) > 0:
|
||||
for poolElement in poolElements:
|
||||
poolName = self._process_tag(poolElement, 'PoolName')
|
||||
if poolName is None:
|
||||
exceptionMessage = (_(
|
||||
"PoolName must be in the file "
|
||||
"%(fileName)s."),
|
||||
{'fileName': fileName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
kwargs = self._fill_record(connargs, serialNumber,
|
||||
poolName, portGroup,
|
||||
poolElement)
|
||||
if interval:
|
||||
kwargs['Interval'] = interval
|
||||
if retries:
|
||||
kwargs['Retries'] = retries
|
||||
myList.append(kwargs)
|
||||
return myList
|
||||
|
||||
def find_volume_by_device_id_on_array(self, conn, storageSystem, deviceID):
|
||||
"""Find the volume by device ID on a specific array.
|
||||
|
||||
|
@ -2134,48 +2259,3 @@ class EMCVMAXUtils(object):
|
|||
{'source': sourceDeviceId, 'storageSystem': storageSystem})
|
||||
|
||||
return foundSyncInstanceName
|
||||
|
||||
def get_smi_version(self, conn):
|
||||
intVersion = 0
|
||||
swIndentityInstances = conn.EnumerateInstances(
|
||||
'SE_ManagementServerSoftwareIdentity')
|
||||
if swIndentityInstances:
|
||||
swIndentityInstance = swIndentityInstances[0]
|
||||
majorVersion = swIndentityInstance['MajorVersion']
|
||||
minorVersion = swIndentityInstance['MinorVersion']
|
||||
revisionNumber = swIndentityInstance['RevisionNumber']
|
||||
|
||||
intVersion = int(str(majorVersion) + str(minorVersion)
|
||||
+ str(revisionNumber))
|
||||
|
||||
LOG.debug("Major version: %(majV)lu, Minor version: %(minV)lu, "
|
||||
"Revision number: %(revNum)lu, Version: %(intV)lu.",
|
||||
{'majV': majorVersion,
|
||||
'minV': minorVersion,
|
||||
'revNum': revisionNumber,
|
||||
'intV': intVersion})
|
||||
return intVersion
|
||||
|
||||
def get_composite_elements(
|
||||
self, conn, volumeInstance):
|
||||
"""Get the meta members of a composite volume.
|
||||
|
||||
:param conn: ECOM connection
|
||||
:param volumeInstance: the volume instance
|
||||
:returns memberVolumes: a list of meta members
|
||||
"""
|
||||
memberVolumes = None
|
||||
storageSystemName = volumeInstance['SystemName']
|
||||
elementCompositionService = self.find_element_composition_service(
|
||||
conn, storageSystemName)
|
||||
rc, ret = conn.InvokeMethod(
|
||||
'GetCompositeElements',
|
||||
elementCompositionService,
|
||||
TheElement=volumeInstance.path)
|
||||
|
||||
if 'OutElements' in ret:
|
||||
LOG.debug("Get composite elements of volume "
|
||||
"%(volume)s rc=%(rc)d, ret=%(ret)s",
|
||||
{'volume': volumeInstance.path, 'rc': rc, 'ret': ret})
|
||||
memberVolumes = ret['OutElements']
|
||||
return memberVolumes
|
||||
|
|
Loading…
Reference in New Issue