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:
Xing Yang 2015-06-09 20:57:32 -04:00
parent f4a6c9fd5e
commit 3cf36e1f1d
10 changed files with 2073 additions and 1091 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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).

View File

@ -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']):

View File

@ -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()

View File

@ -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)

View File

@ -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):

View File

@ -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:

View File

@ -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,

View File

@ -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

View File

@ -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