cinder/cinder/volume/drivers/emc/emc_vmax_utils.py

1172 lines
45 KiB
Python

# Copyright (c) 2012 - 2014 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import random
import re
from xml.dom.minidom import parseString
import six
from cinder import context
from cinder import exception
from cinder.i18n import _
from cinder.openstack.common import log as logging
from cinder.openstack.common import loopingcall
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
try:
import pywbem
pywbemAvailable = True
except ImportError:
pywbemAvailable = False
STORAGEGROUPTYPE = 4
POSTGROUPTYPE = 3
EMC_ROOT = 'root/emc'
CONCATENATED = 'concatenated'
CINDER_EMC_CONFIG_FILE_PREFIX = '/etc/cinder/cinder_emc_config_'
CINDER_EMC_CONFIG_FILE_POSTFIX = '.xml'
ISCSI = 'iscsi'
FC = 'fc'
JOB_RETRIES = 60
INTERVAL_10_SEC = 10
class EMCVMAXUtils(object):
"""Utility class for SMI-S based EMC volume drivers.
This Utility class is for EMC volume drivers based on SMI-S.
It supports VMAX arrays.
"""
def __init__(self, prtcl):
if not pywbemAvailable:
LOG.info(_(
'Module PyWBEM not installed. '
'Install PyWBEM using the python-pywbem package.'))
self.protocol = prtcl
def find_storage_configuration_service(self, conn, storageSystemName):
"""Given the storage system name, get the storage configuration service
:param conn: connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundconfigService
"""
foundConfigService = None
configservices = conn.EnumerateInstanceNames(
'EMC_StorageConfigurationService')
for configservice in configservices:
if storageSystemName == configservice['SystemName']:
foundConfigService = configservice
LOG.debug("Found Storage Configuration Service: "
"%(configservice)s"
% {'configservice': configservice})
break
if foundConfigService is None:
exceptionMessage = (_("Storage Configuration Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundConfigService
def find_controller_configuration_service(self, conn, storageSystemName):
"""Get the controller config by using the storage service name.
Given the storage system name, get the controller configuration
service.
:param conn: connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundconfigService
"""
foundConfigService = None
configservices = conn.EnumerateInstanceNames(
'EMC_ControllerConfigurationService')
for configservice in configservices:
if storageSystemName == configservice['SystemName']:
foundConfigService = configservice
LOG.debug("Found Controller Configuration Service: "
"%(configservice)s"
% {'configservice': configservice})
break
if foundConfigService is None:
exceptionMessage = (_("Controller Configuration Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundConfigService
def find_element_composition_service(self, conn, storageSystemName):
"""Given the storage system name, get the element composition service.
:param conn: the connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundElementCompositionService
"""
foundElementCompositionService = None
elementCompositionServices = conn.EnumerateInstanceNames(
'Symm_ElementCompositionService')
for elementCompositionService in elementCompositionServices:
if storageSystemName == elementCompositionService['SystemName']:
foundElementCompositionService = elementCompositionService
LOG.debug("Found Element Composition Service:"
"%(elementCompositionService)s"
% {'elementCompositionService':
elementCompositionService})
break
if foundElementCompositionService is None:
exceptionMessage = (_("Element Composition Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundElementCompositionService
def find_storage_relocation_service(self, conn, storageSystemName):
"""Given the storage system name, get the storage relocation service.
:param conn: the connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundStorageRelocationService
"""
foundStorageRelocationService = None
storageRelocationServices = conn.EnumerateInstanceNames(
'Symm_StorageRelocationService')
for storageRelocationService in storageRelocationServices:
if storageSystemName == storageRelocationService['SystemName']:
foundStorageRelocationService = storageRelocationService
LOG.debug("Found Element Composition Service:"
"%(storageRelocationService)s"
% {'storageRelocationService':
storageRelocationService})
break
if foundStorageRelocationService is None:
exceptionMessage = (_("Storage Relocation Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundStorageRelocationService
def find_storage_hardwareid_service(self, conn, storageSystemName):
"""Given the storage system name, get the storage hardware service.
:param conn: the connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundStorageRelocationService
"""
foundHardwareService = None
storageHardwareservices = conn.EnumerateInstanceNames(
'EMC_StorageHardwareIDManagementService')
for storageHardwareservice in storageHardwareservices:
if storageSystemName == storageHardwareservice['SystemName']:
foundHardwareService = storageHardwareservice
LOG.debug("Found Storage Hardware ID Management Service:"
"%(storageHardwareservice)s"
% {'storageHardwareservice': storageHardwareservice})
break
if foundHardwareService is None:
exceptionMessage = (_("Storage HardwareId mgmt Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundHardwareService
def find_replication_service(self, conn, storageSystemName):
"""Given the storage system name, get the replication service.
:param conn: the connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundRepService
"""
foundRepService = None
repservices = conn.EnumerateInstanceNames(
'EMC_ReplicationService')
for repservice in repservices:
if storageSystemName == repservice['SystemName']:
foundRepService = repservice
LOG.debug("Found Replication Service:"
"%(repservice)s"
% {'repservice': repservice})
break
if foundRepService is None:
exceptionMessage = (_("Replication Service not found "
"on %(storageSystemName)s")
% {'storageSystemName': storageSystemName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundRepService
def get_tier_policy_service(self, conn, storageSystemInstanceName):
"""Gets the tier policy service for a given storage system instance.
Given the storage system instance name, get the existing tier
policy service.
:param conn: the connection information to the ecom server
:param storageSystemInstanceName: the storageSystem instance Name
:returns: foundTierPolicyService - the tier policy
service instance name
"""
foundTierPolicyService = None
groups = conn.AssociatorNames(
storageSystemInstanceName,
ResultClass='Symm_TierPolicyService',
AssocClass='CIM_HostedService')
if len(groups) > 0:
foundTierPolicyService = groups[0]
if foundTierPolicyService is None:
exceptionMessage = (_(
"Tier Policy Service not found "
"for %(storageSystemName)s")
% {'storageSystemName': storageSystemInstanceName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return foundTierPolicyService
def wait_for_job_complete(self, conn, job):
"""Given the job wait for it to complete.
:param conn: connection to the ecom server
:param job: the job dict
:returns: rc - the return code
:returns: errorDesc - the error description string
"""
jobInstanceName = job['Job']
self._wait_for_job_complete(conn, job)
jobinstance = conn.GetInstance(jobInstanceName,
LocalOnly=False)
rc = jobinstance['ErrorCode']
errorDesc = jobinstance['ErrorDescription']
LOG.debug('Return code is: %(rc)lu'
'Error Description is: %(errorDesc)s'
% {'rc': rc,
'errorDesc': errorDesc})
return rc, errorDesc
def _wait_for_job_complete(self, conn, job):
"""Given the job wait for it to complete.
:param conn: connection to the ecom server
:param job: the job dict
"""
def _wait_for_job_complete():
"""Called at an interval until the job is finished"""
if self._is_job_finished(conn, job):
raise loopingcall.LoopingCallDone()
if self.retries > JOB_RETRIES:
LOG.error(_("_wait_for_job_complete failed after %(retries)d "
"tries") % {'retries': self.retries})
raise loopingcall.LoopingCallDone()
try:
self.retries += 1
if not self.wait_for_job_called:
if self._is_job_finished(conn, job):
self.wait_for_job_called = True
except Exception as e:
LOG.error(_("Exception: %s") % six.text_type(e))
exceptionMessage = (_("Issue encountered waiting for job."))
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(exceptionMessage)
self.retries = 0
self.wait_for_job_called = False
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_job_complete)
timer.start(interval=INTERVAL_10_SEC).wait()
def _is_job_finished(self, conn, job):
"""Check if the job is finished.
:param conn: connection to the ecom server
:param job: the job dict
:returns: True if finished; False if not finished;
"""
jobInstanceName = job['Job']
jobinstance = conn.GetInstance(jobInstanceName,
LocalOnly=False)
jobstate = jobinstance['JobState']
# From ValueMap of JobState in CIM_ConcreteJob
# 2L=New, 3L=Starting, 4L=Running, 32767L=Queue Pending
# ValueMap("2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13..32767,
# 32768..65535"),
# Values("New, Starting, Running, Suspended, Shutting Down,
# Completed, Terminated, Killed, Exception, Service,
# Query Pending, DMTF Reserved, Vendor Reserved")]
# NOTE(deva): string matching based on
# http://ipmitool.cvs.sourceforge.net/
# viewvc/ipmitool/ipmitool/lib/ipmi_chassis.c
if jobstate in [2L, 3L, 4L, 32767L]:
return False
else:
return True
def wait_for_sync(self, conn, syncName):
"""Given the sync name wait for it to fully synchronize.
:param conn: connection to the ecom server
:param syncName: the syncName
"""
def _wait_for_sync():
"""Called at an interval until the synchronization is finished"""
if self._is_sync_complete(conn, syncName):
raise loopingcall.LoopingCallDone()
if self.retries > JOB_RETRIES:
LOG.error(_("_wait_for_sync failed after %(retries)d tries")
% {'retries': self.retries})
raise loopingcall.LoopingCallDone()
try:
self.retries += 1
if not self.wait_for_sync_called:
if self._is_sync_complete(conn, syncName):
self.wait_for_sync_called = True
except Exception as e:
LOG.error(_("Exception: %s") % six.text_type(e))
exceptionMessage = (_("Issue encountered waiting for "
"synchronization."))
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(exceptionMessage)
self.retries = 0
self.wait_for_sync_called = False
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync)
timer.start(interval=INTERVAL_10_SEC).wait()
def _is_sync_complete(self, conn, syncName):
"""Check if the job is finished.
:param conn: connection to the ecom server
:param syncName: the sync name
:returns: True if fully synchronized; False if not;
"""
syncInstance = conn.GetInstance(syncName,
LocalOnly=False)
percentSynced = syncInstance['PercentSynced']
if percentSynced < 100:
return False
else:
return True
def get_num(self, numStr, datatype):
"""Get the ecom int from the number.
:param numStr: the number in string format
:param datatype: the type to convert it to
:returns: result
"""
try:
result = {
'8': pywbem.Uint8(numStr),
'16': pywbem.Uint16(numStr),
'32': pywbem.Uint32(numStr),
'64': pywbem.Uint64(numStr)
}
result = result.get(datatype, numStr)
except NameError:
result = numStr
return result
def find_storage_system(self, conn, configService):
"""Finds the storage system for a particular config service.
Given the storage configuration service get the CIM_StorageSystem
from it.
:param conn: the connection to the ecom server
:param storageConfigService: the storage configuration service
:returns: rc - the return code of the job
:returns: jobDict - the job dict
"""
foundStorageSystemInstanceName = None
groups = conn.AssociatorNames(
configService,
AssocClass='CIM_HostedService')
if len(groups) > 0:
foundStorageSystemInstanceName = groups[0]
else:
exception_message = (_("Cannot get storage system"))
LOG.error(exception_message)
raise
return foundStorageSystemInstanceName
def get_storage_group_from_volume(self, conn, volumeInstanceName):
"""Returns the storage group for a particular volume.
Given the volume instance name get the associated storage group if it
is belong to one
:param conn: connection to the ecom server
:param volumeInstanceName: the volume instance name
:returns: foundStorageGroupInstanceName - the storage group
instance name
"""
foundStorageGroupInstanceName = None
storageGroupInstanceNames = conn.AssociatorNames(
volumeInstanceName,
ResultClass='CIM_DeviceMaskingGroup')
if len(storageGroupInstanceNames) > 0:
foundStorageGroupInstanceName = storageGroupInstanceNames[0]
return foundStorageGroupInstanceName
def find_storage_masking_group(self, conn, controllerConfigService,
storageGroupName):
"""Given the storage group name get the storage group.
:param conn: connection to the ecom server
:param controllerConfigService: the controllerConfigService
:param storageGroupName: the name of the storage group you are getting
:param foundStorageGroup: storage group instance name
"""
foundStorageMaskingGroupInstanceName = None
storageMaskingGroupInstanceNames = (
conn.AssociatorNames(controllerConfigService,
ResultClass='CIM_DeviceMaskingGroup'))
for storageMaskingGroupInstanceName in \
storageMaskingGroupInstanceNames:
storageMaskingGroupInstance = conn.GetInstance(
storageMaskingGroupInstanceName)
if storageGroupName == storageMaskingGroupInstance['ElementName']:
foundStorageMaskingGroupInstanceName = (
storageMaskingGroupInstanceName)
break
return foundStorageMaskingGroupInstanceName
def find_storage_system_name_from_service(self, configService):
"""Given any service get the storage system name from it.
:param configService: the configuration service
:returns: configService['SystemName'] - storage system name (String)
"""
return configService['SystemName']
def find_volume_instance(self, conn, volumeDict, volumeName):
"""Given the volumeDict get the instance from it.
:param conn: connection to the ecom server
:param volumeDict: the volume Dict
:param volumeName: the user friendly name of the volume
:returns: foundVolumeInstance - the volume instance
"""
volumeInstanceName = self.get_instance_name(volumeDict['classname'],
volumeDict['keybindings'])
foundVolumeInstance = conn.GetInstance(volumeInstanceName)
if foundVolumeInstance is None:
LOG.debug("Volume %(volumeName)s not found on the array."
% {'volumeName': volumeName})
else:
LOG.debug("Volume name: %(volumeName)s Volume instance: "
"%(vol_instance)s."
% {'volumeName': volumeName,
'vol_instance': foundVolumeInstance.path})
return foundVolumeInstance
def get_host_short_name(self, hostName):
"""Returns the short name for a given qualified host name.
Checks the host name to see if it is the fully qualified host name
and returns part before the dot. If there is no dot in the hostName
the full hostName is returned.
:param hostName: the fully qualified host name ()
:param shortHostName: the short hostName
"""
shortHostName = None
hostArray = hostName.split('.')
if len(hostArray) > 2:
shortHostName = hostArray[0]
else:
shortHostName = hostName
return shortHostName
def get_instance_name(self, classname, bindings):
"""Get the instance from the classname and bindings.
NOTE: This exists in common too...will be moving it to other file
where both common and masking can access it
:param classname: class name for the volume instance
:param bindings: volume created from job
:returns: foundVolumeInstance - the volume instance
"""
instanceName = None
try:
instanceName = pywbem.CIMInstanceName(
classname,
namespace=EMC_ROOT,
keybindings=bindings)
except NameError:
instanceName = None
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 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 portGroups and choose
one randomly.
:param fileName: the path and name of the file
:returns: portGroupName - the name of the port group chosen
"""
portGroupName = None
myFile = open(fileName, 'r')
data = myFile.read()
myFile.close()
dom = parseString(data)
portGroups = dom.getElementsByTagName('PortGroups')
if portGroups is not None and len(portGroups) > 0:
portGroupsXml = portGroups[0].toxml()
portGroupsXml = portGroupsXml.replace('<PortGroups>', '')
portGroupsXml = portGroupsXml.replace('</PortGroups>', '')
portGroupsXml = portGroupsXml.replace('<PortGroup>', '|')
portGroupsXml = portGroupsXml.replace('</PortGroup>', '')
portGroupsXml = portGroupsXml.replace('\n', '')
portGroupsXml = portGroupsXml.replace('\t', '')
portGroupsXml = portGroupsXml[1:]
# convert the | separated string to a list
portGroupNames = (
[s.strip() for s in portGroupsXml.split('|') if s])
numPortGroups = len(portGroupNames)
portGroupName = (
portGroupNames[random.randint(0, numPortGroups - 1)])
return portGroupName
else:
exception_message = (_("Port Group name not found."))
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
:returns: retString - the returned string
"""
retString = None
myFile = open(fileName, 'r')
data = myFile.read()
myFile.close()
dom = 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('\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})
else:
LOG.info(_("Fast Policy not found."))
return fastPolicyName
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: arrayName - the array name
"""
arrayName = self._parse_from_file(fileName, 'Array')
if not arrayName:
LOG.debug("Array not found from config file.")
return arrayName
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: poolName - the pool name
"""
poolName = self._parse_from_file(fileName, 'Pool')
if not poolName:
LOG.debug("Pool not found from config file.")
return poolName
def parse_pool_instance_id(self, poolInstanceId):
"""Given the instance Id parse the pool name and system name from it.
Example of pool InstanceId: Symmetrix+0001233455555+U+Pool 0
:param poolInstanceId: the path and name of the file
:returns: poolName - the pool name
:returns: systemName - the system name
"""
poolName = None
systemName = None
endp = poolInstanceId.rfind('+')
if endp > -1:
poolName = poolInstanceId[endp + 1:]
idarray = poolInstanceId.split('+')
if len(idarray) > 2:
systemName = idarray[0] + '+' + idarray[1]
LOG.debug("Pool name: %(poolName)s System name: %(systemName)s."
% {'poolName': poolName, 'systemName': systemName})
return poolName, systemName
def convert_gb_to_bits(self, strGbSize):
"""Convert GB(string) to bits(string).
:param strGB: string -- The size in GB
:returns: strBitsSize string -- The size in bits
"""
strBitsSize = six.text_type(int(strGbSize) * 1024 * 1024 * 1024)
LOG.debug("Converted %(strGbSize)s GBs to %(strBitsSize)s Bits"
% {'strGbSize': strGbSize, 'strBitsSize': strBitsSize})
return strBitsSize
def check_if_volume_is_composite(self, conn, volumeInstance):
"""Check if the volume is composite.
:param conn: the connection information to the ecom server
:param volumeInstance: the volume Instance
:returns: 'True', 'False' or 'Undetermined'
"""
propertiesList = volumeInstance.properties.items()
for properties in propertiesList:
if properties[0] == 'IsComposite':
cimProperties = properties[1]
if 'True' in six.text_type(cimProperties.value):
return 'True'
elif 'False' in six.text_type(cimProperties.value):
return 'False'
else:
return 'Undetermined'
return 'Undetermined'
def get_assoc_pool_from_volume(self, conn, volumeInstanceName):
"""Give the volume instance get the associated pool instance
:param conn: connection to the ecom server
:param volumeInstanceName: the volume instance name
:returns: foundPoolInstanceName
"""
foundPoolInstanceName = None
foundPoolInstanceNames = (
conn.AssociatorNames(volumeInstanceName,
ResultClass='EMC_VirtualProvisioningPool'))
if len(foundPoolInstanceNames) > 0:
foundPoolInstanceName = foundPoolInstanceNames[0]
return foundPoolInstanceName
def check_if_volume_is_concatenated(self, conn, volumeInstance):
"""Checks if a volume is concatenated or not.
Check underlying CIM_StorageExtent to see if the volume is
concatenated or not.
If isConcatenated is true then it is a composite
If isConcatenated is False and isVolumeComposite is True then
it is a striped
If isConcatenated is False and isVolumeComposite is False then
it has no composite type and we can proceed.
:param conn: the connection information to the ecom server
:param volumeInstance: the volume instance
:returns: 'True', 'False' or 'Undetermined'
"""
isConcatenated = None
isVolumeComposite = self.check_if_volume_is_composite(
conn, volumeInstance)
storageExtentInstanceNames = conn.AssociatorNames(
volumeInstance.path,
ResultClass='CIM_StorageExtent')
if len(storageExtentInstanceNames) > 0:
storageExtentInstanceName = storageExtentInstanceNames[0]
storageExtentInstance = conn.GetInstance(storageExtentInstanceName)
propertiesList = storageExtentInstance.properties.items()
for properties in propertiesList:
if properties[0] == 'IsConcatenated':
cimProperties = properties[1]
isConcatenated = six.text_type(cimProperties.value)
if isConcatenated is not None:
break
if 'True' in isConcatenated:
return 'True'
elif 'False' in isConcatenated and 'True' in isVolumeComposite:
return 'False'
elif 'False' in isConcatenated and 'False' in isVolumeComposite:
return 'True'
else:
return 'Undetermined'
def get_composite_type(self, compositeTypeStr):
"""Get the int value of composite type.
The default is '2' concatenated.
:param compositeTypeStr: 'concatenated' or 'striped'. Cannot be None
:returns: compositeType = 2 or 3
"""
compositeType = 2
stripedStr = 'striped'
try:
if compositeTypeStr.lower() == stripedStr.lower():
compositeType = 3
except KeyError:
# Default to concatenated if not defined
pass
return compositeType
def is_volume_bound_to_pool(self, conn, volumeInstance):
'''Check if volume is bound to a pool.
:param conn: the connection information to the ecom server
:param storageServiceInstanceName: the storageSystem instance Name
:returns: foundIsSupportsTieringPolicies - true/false
'''
propertiesList = volumeInstance.properties.items()
for properties in propertiesList:
if properties[0] == 'EMCIsBound':
cimProperties = properties[1]
if 'True' in six.text_type(cimProperties.value):
return 'True'
elif 'False' in six.text_type(cimProperties.value):
return 'False'
else:
return 'Undetermined'
return 'Undetermined'
def get_space_consumed(self, conn, volumeInstance):
'''Check the space consumed of a volume.
:param conn: the connection information to the ecom server
:param volumeInstance: the volume Instance
:returns: spaceConsumed
'''
foundSpaceConsumed = None
unitnames = conn.References(
volumeInstance, ResultClass='CIM_AllocatedFromStoragePool',
Role='Dependent')
for unitname in unitnames:
propertiesList = unitname.properties.items()
for properties in propertiesList:
if properties[0] == 'SpaceConsumed':
cimProperties = properties[1]
foundSpaceConsumed = cimProperties.value
break
if foundSpaceConsumed is not None:
break
return foundSpaceConsumed
def get_volume_size(self, conn, volumeInstance):
'''Get the volume size.
ConsumableBlocks * BlockSize
:param conn: the connection information to the ecom server
:param volumeInstance: the volume Instance
:returns: volumeSizeOut
'''
volumeSizeOut = 'Undetermined'
numBlocks = 0
blockSize = 0
propertiesList = volumeInstance.properties.items()
for properties in propertiesList:
if properties[0] == 'ConsumableBlocks':
cimProperties = properties[1]
numBlocks = int(cimProperties.value)
if properties[0] == 'BlockSize':
cimProperties = properties[1]
blockSize = int(cimProperties.value)
if blockSize > 0 and numBlocks > 0:
break
if blockSize > 0 and numBlocks > 0:
volumeSizeOut = six.text_type(numBlocks * blockSize)
return volumeSizeOut
def determine_member_count(self, sizeStr, memberCount, compositeType):
'''Determines how many members a volume should contain.
Based on the size of the proposed volume, the compositeType and the
memberCount, determine (or validate) how many meta members there
should be in a volume.
:param sizeStr: the size in GBs of the proposed volume
:param memberCount: the initial member count
:param compositeType: the composite type
:returns: memberCount - string
:returns: errorDesc - the error description
'''
errorDesc = None
if compositeType in 'concatenated' and int(sizeStr) > 240:
newMemberCount = int(sizeStr) / 240
modular = int(sizeStr) % 240
if modular > 0:
newMemberCount += 1
memberCount = six.text_type(newMemberCount)
if compositeType in 'striped':
metaSize = int(sizeStr) / int(memberCount)
modular = int(sizeStr) % int(memberCount)
metaSize = metaSize + modular
if metaSize > 240:
errorDesc = ('Meta Size is greater than maximum allowed meta '
'size')
return memberCount, errorDesc
def get_extra_specs_by_volume_type_name(self, volumeTypeName):
"""Gets the extra specs associated with a volume type.
Given the string value of the volume type name, get the extra specs
object associated with the volume type
:param volumeTypeName: string value of the volume type name
:returns: extra_specs - extra specs object
"""
ctxt = context.get_admin_context()
volume_type = volume_types.get_volume_type_by_name(
ctxt, volumeTypeName)
extra_specs = volume_type['extra_specs']
return extra_specs
def get_pool_capacities(self, conn, poolName, storageSystemName):
"""Get the total and remaining capacity in GB for a storage pool.
Given the storage pool name, get the total capacity and remaining
capacity in GB
:param conn: connection to the ecom server
:param storagePoolName: string value of the storage pool name
:returns: total_capacity_gb - total capacity of the storage pool in GB
:returns: free_capacity_gb - remaining capacity of the
storage pool in GB
"""
LOG.debug("Retrieving capacity for pool %(poolName)s on array "
"%(array)s"
% {'poolName': poolName,
'array': storageSystemName})
poolInstanceName = self.get_pool_by_name(
conn, poolName, storageSystemName)
if poolInstanceName is None:
LOG.error("Unable to retrieve pool instance of %(poolName)s on "
"array %(array)s"
% {'poolName': poolName,
'array': storageSystemName})
return (0, 0)
storagePoolInstance = conn.GetInstance(
poolInstanceName, LocalOnly=False)
total_capacity_gb = self.convert_bits_to_gbs(
storagePoolInstance['TotalManagedSpace'])
allocated_capacity_gb = self.convert_bits_to_gbs(
storagePoolInstance['EMCSubscribedCapacity'])
free_capacity_gb = total_capacity_gb - allocated_capacity_gb
return (total_capacity_gb, free_capacity_gb)
def get_pool_by_name(self, conn, storagePoolName, storageSystemName):
"""Returns the instance name associated with a storage pool name.
:param conn: connection to the ecom server
:param storagePoolName: string value of the storage pool name
:param storageSystemName: string value of array
:returns: poolInstanceName - instance name of storage pool
"""
poolInstanceName = None
LOG.debug("storagePoolName: %(poolName)s, storageSystemName: %(array)s"
% {'poolName': storagePoolName,
'array': storageSystemName})
poolInstanceNames = conn.EnumerateInstanceNames(
'EMC_VirtualProvisioningPool')
for pool in poolInstanceNames:
poolName, systemName = (
self.parse_pool_instance_id(pool['InstanceID']))
if (poolName == storagePoolName and
storageSystemName in systemName):
poolInstanceName = pool
return poolInstanceName
def convert_bits_to_gbs(self, strBitSize):
"""Convert Bits(string) to GB(string).
:param strBitSize: string -- The size in bits
:returns: gbSize string -- The size in GB
"""
gbSize = int(strBitSize) / 1024 / 1024 / 1024
return gbSize
def compare_size(self, size1Str, size2Str):
"""Compare the bit sizes to an approximate.
:param size1Str: the first bit size (String)
:param size2Str: the second bit size (String)
:returns: size1GBs - size2GBs (int)
"""
size1GBs = self.convert_bits_to_gbs(size1Str)
size2GBs = self.convert_bits_to_gbs(size2Str)
return size1GBs - size2GBs
def get_volumetype_extraspecs(self, volume):
"""Compare the bit sizes to an approximate.
:param volume: the volume dictionary
:returns: extraSpecs - the extra specs
"""
extraSpecs = {}
try:
type_id = volume['volume_type_id']
if type_id is not None:
extraSpecs = volume_types.get_volume_type_extra_specs(type_id)
except Exception:
pass
return extraSpecs
def get_volume_type_name(self, volume):
"""Get the volume type name.
:param volume: the volume dictionary
:returns: volumeTypeName - the volume type name
"""
volumeTypeName = None
ctxt = context.get_admin_context()
typeId = volume['volume_type_id']
if typeId is not None:
volumeType = volume_types.get_volume_type(ctxt, typeId)
volumeTypeName = volumeType['name']
return volumeTypeName
def parse_volume_type_from_filename(self, emcConfigFile):
"""Parse the volume type from the file (if it exists).
:param emcConfigFile: the EMC configuration file
:returns: volumeTypeName - the volume type name
"""
volumeTypeName = None
m = re.search('/etc/cinder/cinder_emc_config_(.+?).xml', emcConfigFile)
if m:
volumeTypeName = m.group(1)
return volumeTypeName
def get_volumes_from_pool(self, conn, poolInstanceName):
'''Check the space consumed of a volume.
:param conn: the connection information to the ecom server
:param volumeInstance: the volume Instance
:returns: spaceConsumed
'''
return conn.AssociatorNames(
poolInstanceName, AssocClass='CIM_AllocatedFromStoragePool',
ResultClass='CIM_StorageVolume')
def check_is_volume_bound_to_pool(self, conn, volumeInstance):
'''Check the space consumed of a volume.
:param conn: the connection information to the ecom server
:param volumeInstance: the volume Instance
:returns: spaceConsumed
'''
foundSpaceConsumed = None
unitnames = conn.References(
volumeInstance, ResultClass='CIM_AllocatedFromStoragePool',
Role='Dependent')
for unitname in unitnames:
propertiesList = unitname.properties.items()
for properties in propertiesList:
if properties[0] == 'EMCBoundToThinStoragePool':
cimProperties = properties[1]
foundSpaceConsumed = cimProperties.value
break
if foundSpaceConsumed is not None:
break
if 'True' in six.text_type(cimProperties.value):
return 'True'
elif 'False' in six.text_type(cimProperties.value):
return 'False'
else:
return 'Undetermined'
def get_short_protocol_type(self, protocol):
'''Given the protocol type, return I for iscsi and F for fc
:param protocol: iscsi or fc
:returns: 'I' or 'F'
'''
if protocol.lower() == ISCSI.lower():
return 'I'
elif protocol.lower() == FC.lower():
return 'F'
else:
return protocol
def get_hardware_id_instance_names_from_array(
self, conn, hardwareIdManagementService):
"""Get all the hardware ids from an array.
:param conn: connection to the ecom server
:param: hardwareIdManagementService - hardware id management service
:returns: hardwareIdInstanceNames - the list of hardware
id instance names
"""
hardwareIdInstanceNames = (
conn.AssociatorNames(hardwareIdManagementService,
ResultClass='SE_StorageHardwareID'))
return hardwareIdInstanceNames
def find_ip_protocol_endpoint(self, conn, storageSystemName):
'''Find the IP protocol endpoint for ISCSI.
:param conn: the connection to the ecom server
:param storageSystemName: the storage system name
:returns: foundIpAddress
'''
foundIpAddress = None
ipProtocolEndpointInstances = conn.EnumerateInstances(
'CIM_IPProtocolEndpoint')
for ipProtocolEndpointInstance in ipProtocolEndpointInstances:
ipStorageSystemName = (
ipProtocolEndpointInstance.path['SystemName'])
if storageSystemName in ipStorageSystemName:
propertiesList = (
ipProtocolEndpointInstance.properties.items())
for properties in propertiesList:
if properties[0] == 'IPv4Address':
cimProperties = properties[1]
foundIpAddress = cimProperties.value
if (foundIpAddress == '127.0.0.1'
or foundIpAddress == '0.0.0.0'):
foundIpAddress = None
else:
break
if foundIpAddress is not None:
break
return foundIpAddress