2838 lines
111 KiB
Python
2838 lines
111 KiB
Python
# Copyright (c) 2012 - 2015 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 datetime
|
|
import hashlib
|
|
import os
|
|
import pickle
|
|
import random
|
|
import re
|
|
import time
|
|
from xml.dom import minidom
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_service import loopingcall
|
|
from oslo_utils import units
|
|
import six
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LE, _LI, _LW
|
|
from cinder.volume import volume_types
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
try:
|
|
import pywbem
|
|
pywbemAvailable = True
|
|
except ImportError:
|
|
pywbemAvailable = False
|
|
|
|
STORAGEGROUPTYPE = 4
|
|
POSTGROUPTYPE = 3
|
|
CLONE_REPLICATION_TYPE = 10
|
|
SYNC_SNAPSHOT_LOCAL = 6
|
|
ASYNC_SNAPSHOT_LOCAL = 7
|
|
MAX_POOL_LENGTH = 16
|
|
MAX_FASTPOLICY_LENGTH = 14
|
|
|
|
EMC_ROOT = 'root/emc'
|
|
CONCATENATED = 'concatenated'
|
|
CINDER_EMC_CONFIG_FILE_PREFIX = '/etc/cinder/cinder_emc_config_'
|
|
CINDER_EMC_CONFIG_FILE_POSTFIX = '.xml'
|
|
LIVE_MIGRATION_FILE = '/etc/cinder/livemigrationarray'
|
|
ISCSI = 'iscsi'
|
|
FC = 'fc'
|
|
JOB_RETRIES = 60
|
|
INTERVAL_10_SEC = 10
|
|
INTERVAL = 'storagetype:interval'
|
|
RETRIES = 'storagetype:retries'
|
|
CIM_ERR_NOT_FOUND = 6
|
|
VOLUME_ELEMENT_NAME_PREFIX = 'OS-'
|
|
SYNCHRONIZED = 4
|
|
|
|
|
|
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.
|
|
"""
|
|
SLO = 'storagetype:slo'
|
|
WORKLOAD = 'storagetype:workload'
|
|
POOL = 'storagetype:pool'
|
|
|
|
def __init__(self, prtcl):
|
|
if not pywbemAvailable:
|
|
LOG.info(_LI(
|
|
"Module PyWBEM not installed. "
|
|
"Install PyWBEM using the python-pywbem package."))
|
|
self.protocol = prtcl
|
|
|
|
def find_storage_configuration_service(self, conn, storageSystemName):
|
|
"""Get storage configuration service with given storage system name.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storageSystemName: the storage system name
|
|
:returns: foundConfigService
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
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, extraSpecs=None):
|
|
"""Given the job wait for it to complete.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param job: the job dict
|
|
:param extraSpecs: the extraSpecs dict. Defaults to None
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
|
|
def _wait_for_job_complete():
|
|
# Called at an interval until the job is finished.
|
|
retries = kwargs['retries']
|
|
try:
|
|
kwargs['retries'] = retries + 1
|
|
if not kwargs['wait_for_job_called']:
|
|
if self._is_job_finished(conn, job):
|
|
kwargs['rc'], kwargs['errordesc'] = (
|
|
self._verify_job_state(conn, job))
|
|
kwargs['wait_for_job_called'] = True
|
|
except Exception:
|
|
exceptionMessage = (_("Issue encountered waiting for job."))
|
|
LOG.exception(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(exceptionMessage)
|
|
|
|
if retries > maxJobRetries:
|
|
kwargs['rc'], kwargs['errordesc'] = (
|
|
self._verify_job_state(conn, job))
|
|
LOG.error(_LE("_wait_for_job_complete "
|
|
"failed after %(retries)d "
|
|
"tries."),
|
|
{'retries': retries})
|
|
|
|
raise loopingcall.LoopingCallDone()
|
|
if kwargs['wait_for_job_called']:
|
|
raise loopingcall.LoopingCallDone()
|
|
maxJobRetries = self._get_max_job_retries(extraSpecs)
|
|
kwargs = {'retries': 0,
|
|
'wait_for_job_called': False,
|
|
'rc': 0,
|
|
'errordesc': None}
|
|
|
|
intervalInSecs = self._get_interval_in_secs(extraSpecs)
|
|
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_job_complete)
|
|
timer.start(interval=intervalInSecs).wait()
|
|
LOG.debug("Return code is: %(rc)lu. "
|
|
"Error Description is: %(errordesc)s.",
|
|
{'rc': kwargs['rc'],
|
|
'errordesc': kwargs['errordesc']})
|
|
return kwargs['rc'], kwargs['errordesc']
|
|
|
|
def _get_max_job_retries(self, extraSpecs):
|
|
"""Get max job retries either default or user defined
|
|
|
|
:param extraSpecs: extraSpecs dict
|
|
:returns: JOB_RETRIES or user defined
|
|
"""
|
|
if extraSpecs and RETRIES in extraSpecs:
|
|
jobRetries = extraSpecs[RETRIES]
|
|
else:
|
|
jobRetries = JOB_RETRIES
|
|
return int(jobRetries)
|
|
|
|
def _get_interval_in_secs(self, extraSpecs):
|
|
"""Get interval in secs, either default or user defined
|
|
|
|
:param extraSpecs: extraSpecs dict
|
|
:returns: INTERVAL_10_SEC or user defined
|
|
"""
|
|
if extraSpecs and INTERVAL in extraSpecs:
|
|
intervalInSecs = extraSpecs[INTERVAL]
|
|
else:
|
|
intervalInSecs = INTERVAL_10_SEC
|
|
return int(intervalInSecs)
|
|
|
|
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: boolean -- 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
|
|
# 2=New, 3=Starting, 4=Running, 32767=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")]
|
|
if jobstate in [2, 3, 4, 32767]:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def _verify_job_state(self, conn, job):
|
|
"""Check if the job is finished.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param job: the job dict
|
|
:returns: boolean -- True if finished; False if not finished;
|
|
"""
|
|
jobstatedict = {2: 'New',
|
|
3: 'Starting',
|
|
4: 'Running',
|
|
5: 'Suspended',
|
|
6: 'Shutting Down',
|
|
7: 'Completed',
|
|
8: 'Terminated',
|
|
9: 'Killed',
|
|
10: 'Exception',
|
|
11: 'Service',
|
|
32767: 'Queue Pending',
|
|
32768: 'DMTF Reserved',
|
|
65535: 'Vendor Reserved'}
|
|
jobInstanceName = job['Job']
|
|
jobinstance = conn.GetInstance(jobInstanceName,
|
|
LocalOnly=False)
|
|
operationalstatus = jobinstance['OperationalStatus']
|
|
if not operationalstatus:
|
|
jobstate = jobinstance['JobState']
|
|
errordescription = (_(
|
|
"The job has not completed and is in a %(state)s "
|
|
"state.")
|
|
% {'state': jobstatedict[int(jobstate)]})
|
|
LOG.error(errordescription)
|
|
errorcode = -1
|
|
else:
|
|
errordescription = jobinstance['ErrorDescription']
|
|
errorcode = jobinstance['ErrorCode']
|
|
return errorcode, errordescription
|
|
|
|
def wait_for_sync(self, conn, syncName, extraSpecs=None):
|
|
"""Given the sync name wait for it to fully synchronize.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param syncName: the syncName
|
|
:param extraSpecs: extra specifications
|
|
:raises: loopingcall.LoopingCallDone
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
|
|
def _wait_for_sync():
|
|
"""Called at an interval until the synchronization is finished.
|
|
|
|
:raises: loopingcall.LoopingCallDone
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
retries = kwargs['retries']
|
|
try:
|
|
kwargs['retries'] = retries + 1
|
|
if not kwargs['wait_for_sync_called']:
|
|
if self._is_sync_complete(conn, syncName):
|
|
kwargs['wait_for_sync_called'] = True
|
|
except Exception:
|
|
exceptionMessage = (_("Issue encountered waiting for "
|
|
"synchronization."))
|
|
LOG.exception(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(exceptionMessage)
|
|
|
|
if kwargs['retries'] > maxJobRetries:
|
|
LOG.error(_LE("_wait_for_sync failed after %(retries)d "
|
|
"tries."),
|
|
{'retries': retries})
|
|
raise loopingcall.LoopingCallDone(retvalue=maxJobRetries)
|
|
if kwargs['wait_for_sync_called']:
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
maxJobRetries = self._get_max_job_retries(extraSpecs)
|
|
kwargs = {'retries': 0,
|
|
'wait_for_sync_called': False}
|
|
intervalInSecs = self._get_interval_in_secs(extraSpecs)
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync)
|
|
rc = timer.start(interval=intervalInSecs).wait()
|
|
return rc
|
|
|
|
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)
|
|
copyState = syncInstance['CopyState']
|
|
LOG.debug("CopyState is %(copyState)lu.",
|
|
{'copyState': copyState})
|
|
|
|
return copyState == SYNCHRONIZED
|
|
|
|
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 configService: the storage configuration service
|
|
:returns: int -- rc - the return code of the job
|
|
:returns: dict -- jobDict - the job dict
|
|
"""
|
|
foundStorageSystemInstanceName = None
|
|
groups = conn.AssociatorNames(
|
|
configService,
|
|
AssocClass='CIM_HostedService')
|
|
|
|
if len(groups) > 0:
|
|
foundStorageSystemInstanceName = groups[0]
|
|
else:
|
|
LOG.error(_LE("Cannot get storage system."))
|
|
raise
|
|
|
|
return foundStorageSystemInstanceName
|
|
|
|
def get_storage_group_from_volume(self, conn, volumeInstanceName, sgName):
|
|
"""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
|
|
:param sgName: the storage group name
|
|
:returns: foundStorageGroupInstanceName
|
|
"""
|
|
foundStorageGroupInstanceName = None
|
|
|
|
storageGroupInstanceNames = conn.AssociatorNames(
|
|
volumeInstanceName,
|
|
ResultClass='CIM_DeviceMaskingGroup')
|
|
|
|
if len(storageGroupInstanceNames) > 1:
|
|
LOG.info(_LI(
|
|
"The volume belongs to more than one storage group. "
|
|
"Returning storage group %(sgName)s."),
|
|
{'sgName': sgName})
|
|
for storageGroupInstanceName in storageGroupInstanceNames:
|
|
instance = self.get_existing_instance(
|
|
conn, storageGroupInstanceName)
|
|
if instance and sgName == instance['ElementName']:
|
|
foundStorageGroupInstanceName = storageGroupInstanceName
|
|
break
|
|
|
|
return foundStorageGroupInstanceName
|
|
|
|
def get_storage_groups_from_volume(self, conn, volumeInstanceName):
|
|
"""Returns all the storage group for a particular volume.
|
|
|
|
Given the volume instance name get all the associated storage groups.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param volumeInstanceName: the volume instance name
|
|
:returns: foundStorageGroupInstanceName
|
|
"""
|
|
storageGroupInstanceNames = conn.AssociatorNames(
|
|
volumeInstanceName,
|
|
ResultClass='CIM_DeviceMaskingGroup')
|
|
|
|
if storageGroupInstanceNames:
|
|
LOG.debug("There are %(len)d storage groups associated "
|
|
"with volume %(volumeInstanceName)s.",
|
|
{'len': len(storageGroupInstanceNames),
|
|
'volumeInstanceName': volumeInstanceName})
|
|
else:
|
|
LOG.debug("There are no storage groups associated "
|
|
"with volume %(volumeInstanceName)s.",
|
|
{'volumeInstanceName': volumeInstanceName})
|
|
|
|
return storageGroupInstanceNames
|
|
|
|
def wrap_get_storage_group_from_volume(self, conn, volumeInstanceName,
|
|
sgName):
|
|
"""Unit test aid"""
|
|
return self.get_storage_group_from_volume(conn, volumeInstanceName,
|
|
sgName)
|
|
|
|
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
|
|
:returns: foundStorageMaskingGroupInstanceName
|
|
"""
|
|
foundStorageMaskingGroupInstanceName = None
|
|
|
|
storageMaskingGroupInstances = (
|
|
conn.Associators(controllerConfigService,
|
|
ResultClass='CIM_DeviceMaskingGroup'))
|
|
|
|
for storageMaskingGroupInstance in storageMaskingGroupInstances:
|
|
|
|
if storageGroupName == storageMaskingGroupInstance['ElementName']:
|
|
# Check that it has not been deleted recently.
|
|
instance = self.get_existing_instance(
|
|
conn, storageMaskingGroupInstance.path)
|
|
if instance is None:
|
|
# Storage group not found.
|
|
foundStorageMaskingGroupInstanceName = None
|
|
else:
|
|
foundStorageMaskingGroupInstanceName = (
|
|
storageMaskingGroupInstance.path)
|
|
|
|
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: string -- configService['SystemName'] - storage system name
|
|
"""
|
|
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 found 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 ()
|
|
:returns: string -- the short hostName
|
|
"""
|
|
shortHostName = None
|
|
|
|
hostArray = hostName.split('.')
|
|
if len(hostArray) > 1:
|
|
shortHostName = hostArray[0]
|
|
else:
|
|
shortHostName = hostName
|
|
|
|
return self.generate_unique_trunc_host(shortHostName)
|
|
|
|
def get_instance_name(self, classname, bindings):
|
|
"""Get the instance from the classname and bindings.
|
|
|
|
:param classname: class name for the volume instance
|
|
:param bindings: volume created from job
|
|
:returns: pywbem.CIMInstanceName -- instanceName
|
|
"""
|
|
instanceName = None
|
|
try:
|
|
instanceName = pywbem.CIMInstanceName(
|
|
classname,
|
|
namespace=EMC_ROOT,
|
|
keybindings=bindings)
|
|
except NameError:
|
|
instanceName = None
|
|
|
|
return instanceName
|
|
|
|
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: string -- poolName - the pool name
|
|
:returns: string -- 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 = self._format_system_name(idarray[0], idarray[1], '+')
|
|
|
|
LOG.debug("Pool name: %(poolName)s System name: %(systemName)s.",
|
|
{'poolName': poolName, 'systemName': systemName})
|
|
return poolName, systemName
|
|
|
|
def _format_system_name(self, part1, part2, sep):
|
|
"""Join to make up system name
|
|
|
|
:param part1: the prefix
|
|
:param sep: the separator
|
|
:param part2: the postfix
|
|
:returns: systemName
|
|
"""
|
|
return ("%(part1)s%(sep)s%(part2)s"
|
|
% {'part1': part1,
|
|
'sep': sep,
|
|
'part2': part2})
|
|
|
|
def parse_pool_instance_id_v3(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 + 3:]
|
|
|
|
idarray = poolInstanceId.split('-+-')
|
|
if len(idarray) > 2:
|
|
systemName = (
|
|
self._format_system_name(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 bytes(string).
|
|
|
|
:param strGB: string -- The size in GB
|
|
:returns: string -- The size in bytes
|
|
"""
|
|
strBitsSize = six.text_type(int(strGbSize) * units.Gi)
|
|
|
|
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: string -- '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_extendable(self, conn, volumeInstance):
|
|
"""Checks if a volume is extendable or not.
|
|
|
|
Check underlying CIM_StorageExtent to see if the volume is
|
|
concatenated or not.
|
|
If isConcatenated is true then it is a concatenated and
|
|
extendable.
|
|
If isConcatenated is False and isVolumeComposite is True then
|
|
it is striped and not extendable.
|
|
If isConcatenated is False and isVolumeComposite is False then
|
|
it has one member only but is still extendable.
|
|
|
|
:param conn: the connection information to the ecom server
|
|
:param volumeInstance: the volume instance
|
|
:returns: string -- 'True', 'False' or 'Undetermined'
|
|
"""
|
|
isConcatenated = None
|
|
|
|
isVolumeComposite = self.check_if_volume_is_composite(
|
|
conn, volumeInstance)
|
|
|
|
storageExtentInstances = conn.Associators(
|
|
volumeInstance.path,
|
|
ResultClass='CIM_StorageExtent')
|
|
|
|
if len(storageExtentInstances) > 0:
|
|
storageExtentInstance = storageExtentInstances[0]
|
|
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: int -- compositeType = 2 for concatenated, or 3 for striped
|
|
"""
|
|
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 volumeInstance: the volume instance
|
|
:returns: string -- 'True' 'False' or 'Undetermined'
|
|
"""
|
|
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 which is ConsumableBlocks * BlockSize.
|
|
|
|
:param conn: the connection information to the ecom server
|
|
:param volumeInstance: the volume Instance
|
|
:returns: string -- 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: string -- memberCount
|
|
:returns: string -- 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 poolName: string value of the storage pool name
|
|
:param storageSystemName: the storage system name
|
|
:returns: tuple -- (total_capacity_gb, free_capacity_gb,
|
|
provisioned_capacity_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(_LE(
|
|
"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'])
|
|
provisioned_capacity_gb = self.convert_bits_to_gbs(
|
|
storagePoolInstance['EMCSubscribedCapacity'])
|
|
free_capacity_gb = self.convert_bits_to_gbs(
|
|
storagePoolInstance['RemainingManagedSpace'])
|
|
try:
|
|
array_max_over_subscription = self.get_ratio_from_max_sub_per(
|
|
storagePoolInstance['EMCMaxSubscriptionPercent'])
|
|
except KeyError:
|
|
array_max_over_subscription = 65534
|
|
return (total_capacity_gb, free_capacity_gb,
|
|
provisioned_capacity_gb, array_max_over_subscription)
|
|
|
|
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: foundPoolInstanceName - instance name of storage pool
|
|
"""
|
|
foundPoolInstanceName = None
|
|
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')
|
|
for poolInstanceName in poolInstanceNames:
|
|
poolName = self.get_pool_name(conn, poolInstanceName)
|
|
if (poolName == storagePoolName):
|
|
# Check that the pool hasn't been recently deleted.
|
|
instance = self.get_existing_instance(conn, poolInstanceName)
|
|
if instance is None:
|
|
foundPoolInstanceName = None
|
|
else:
|
|
foundPoolInstanceName = poolInstanceName
|
|
break
|
|
|
|
return foundPoolInstanceName
|
|
|
|
def convert_bits_to_gbs(self, strBitSize):
|
|
"""Convert bytes(string) to GB(string).
|
|
|
|
:param strBitSize: string -- The size in bytes
|
|
:returns: int -- 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: int -- size1GBs - size2GBs
|
|
"""
|
|
size1GBs = self.convert_bits_to_gbs(size1Str)
|
|
size2GBs = self.convert_bits_to_gbs(size2Str)
|
|
|
|
return size1GBs - size2GBs
|
|
|
|
def get_volumetype_extraspecs(self, volume, volumeTypeId=None):
|
|
"""Compare the bit sizes to an approximate.
|
|
|
|
:param volume: the volume dictionary
|
|
:param volumeTypeId: Optional override for volume['volume_type_id']
|
|
:returns: dict -- extraSpecs - the extra specs
|
|
"""
|
|
extraSpecs = {}
|
|
|
|
try:
|
|
if volumeTypeId:
|
|
type_id = volumeTypeId
|
|
else:
|
|
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_volumetype_qosspecs(self, volume, volumeTypeId=None):
|
|
"""Get the qos specs.
|
|
|
|
:param volume: the volume dictionary
|
|
:param volumeTypeId: Optional override for volume['volume_type_id']
|
|
:returns: dict -- qosSpecs - the qos specs
|
|
"""
|
|
qosSpecs = {}
|
|
|
|
try:
|
|
if volumeTypeId:
|
|
type_id = volumeTypeId
|
|
else:
|
|
type_id = volume['volume_type_id']
|
|
if type_id is not None:
|
|
qosSpecs = volume_types.get_volume_type_qos_specs(type_id)
|
|
|
|
except Exception:
|
|
LOG.debug("Unable to get QoS specifications.")
|
|
|
|
return qosSpecs
|
|
|
|
def get_volume_type_name(self, volume):
|
|
"""Get the volume type name.
|
|
|
|
:param volume: the volume dictionary
|
|
:returns: string -- 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 poolInstanceName: the pool instance name
|
|
:returns: the volumes in the pool
|
|
"""
|
|
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: string -- 'True', 'False' or 'Undetermined'
|
|
"""
|
|
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: string -- 'I' for iscsi or 'F' for fc
|
|
"""
|
|
if protocol.lower() == ISCSI.lower():
|
|
return 'I'
|
|
elif protocol.lower() == FC.lower():
|
|
return 'F'
|
|
else:
|
|
return protocol
|
|
|
|
def get_hardware_id_instances_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: hardwareIdInstances - the list of hardware
|
|
id instances
|
|
"""
|
|
hardwareIdInstances = (
|
|
conn.Associators(hardwareIdManagementService,
|
|
ResultClass='EMC_StorageHardwareID'))
|
|
|
|
return hardwareIdInstances
|
|
|
|
def truncate_string(self, strToTruncate, maxNum):
|
|
"""Truncate a string by taking first and last characters.
|
|
|
|
:param strToTruncate: the string to be truncated
|
|
:param maxNum: the maximum number of characters
|
|
:returns: string -- truncated string or original string
|
|
"""
|
|
if len(strToTruncate) > maxNum:
|
|
newNum = len(strToTruncate) - maxNum // 2
|
|
firstChars = strToTruncate[:maxNum // 2]
|
|
lastChars = strToTruncate[newNum:]
|
|
strToTruncate = firstChars + lastChars
|
|
|
|
return strToTruncate
|
|
|
|
def get_array(self, host):
|
|
"""Extract the array from the host capabilites.
|
|
|
|
:param host: the host object
|
|
:returns: storageSystem - storage system represents the array
|
|
"""
|
|
|
|
try:
|
|
if '@' in host:
|
|
infoDetail = host.split('@')
|
|
storageSystem = 'SYMMETRIX+' + infoDetail[0]
|
|
except Exception:
|
|
LOG.error(_LE("Error parsing array from host capabilities."))
|
|
|
|
return storageSystem
|
|
|
|
def get_time_delta(self, startTime, endTime):
|
|
"""Get the delta between start and end time.
|
|
|
|
:param startTime: the start time
|
|
:param endTime: the end time
|
|
:returns: string -- delta in string H:MM:SS
|
|
"""
|
|
delta = endTime - startTime
|
|
return six.text_type(datetime.timedelta(seconds=int(delta)))
|
|
|
|
def find_sync_sv_by_volume(
|
|
self, conn, storageSystem, volumeInstance, extraSpecs,
|
|
waitforsync=True):
|
|
"""Find the storage synchronized name by device ID.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storageSystem: the storage system name
|
|
:param volumeInstance: volume instance
|
|
:param extraSpecs: the extraSpecs dict
|
|
:param waitforsync: wait for the synchronization to complete if True
|
|
:returns: foundSyncInstanceName
|
|
"""
|
|
foundSyncInstanceName = None
|
|
syncInstanceNames = conn.EnumerateInstanceNames(
|
|
'SE_StorageSynchronized_SV_SV')
|
|
for syncInstanceName in syncInstanceNames:
|
|
syncSvTarget = syncInstanceName['SyncedElement']
|
|
syncSvSource = syncInstanceName['SystemElement']
|
|
if storageSystem != syncSvTarget['SystemName']:
|
|
continue
|
|
if syncSvTarget['DeviceID'] == volumeInstance['DeviceID'] or (
|
|
syncSvSource['DeviceID'] == volumeInstance['DeviceID']):
|
|
# Check that it hasn't recently been deleted.
|
|
try:
|
|
conn.GetInstance(syncInstanceName)
|
|
foundSyncInstanceName = syncInstanceName
|
|
LOG.debug("Found sync Name: "
|
|
"%(syncName)s.",
|
|
{'syncName': foundSyncInstanceName})
|
|
except Exception:
|
|
foundSyncInstanceName = None
|
|
break
|
|
|
|
if foundSyncInstanceName:
|
|
# Wait for SE_StorageSynchronized_SV_SV to be fully synced.
|
|
if waitforsync:
|
|
LOG.warning(_LW(
|
|
"Expect a performance hit as volume is not fully "
|
|
"synced on %(deviceId)s."),
|
|
{'deviceId': volumeInstance['DeviceID']})
|
|
startTime = time.time()
|
|
self.wait_for_sync(conn, foundSyncInstanceName, extraSpecs)
|
|
LOG.warning(_LW(
|
|
"Synchronization process took "
|
|
"took: %(delta)s H:MM:SS."),
|
|
{'delta': self.get_time_delta(startTime,
|
|
time.time())})
|
|
|
|
return foundSyncInstanceName
|
|
|
|
def find_group_sync_rg_by_target(
|
|
self, conn, storageSystem, targetRgInstanceName, extraSpecs,
|
|
waitforsync=True):
|
|
"""Find the SE_GroupSynchronized_RG_RG instance name by target group.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storageSystem: the storage system name
|
|
:param targetRgInstanceName: target group instance name
|
|
:param extraSpecs: the extraSpecs dict
|
|
:param waitforsync: wait for synchronization to complete
|
|
:returns: foundSyncInstanceName
|
|
"""
|
|
foundSyncInstanceName = None
|
|
groupSyncRgInstanceNames = conn.EnumerateInstanceNames(
|
|
'SE_GroupSynchronized_RG_RG')
|
|
for rgInstanceName in groupSyncRgInstanceNames:
|
|
rgTarget = rgInstanceName['SyncedElement']
|
|
if targetRgInstanceName['InstanceID'] == rgTarget['InstanceID']:
|
|
# Check that it has not recently been deleted.
|
|
try:
|
|
conn.GetInstance(rgInstanceName)
|
|
foundSyncInstanceName = rgInstanceName
|
|
LOG.debug("Found group sync name: "
|
|
"%(syncName)s.",
|
|
{'syncName': foundSyncInstanceName})
|
|
except Exception:
|
|
foundSyncInstanceName = None
|
|
break
|
|
|
|
if foundSyncInstanceName is None:
|
|
LOG.warning(_LW(
|
|
"Group sync name not found for target group %(target)s "
|
|
"on %(storageSystem)s."),
|
|
{'target': targetRgInstanceName['InstanceID'],
|
|
'storageSystem': storageSystem})
|
|
else:
|
|
# Wait for SE_StorageSynchronized_SV_SV to be fully synced.
|
|
if waitforsync:
|
|
self.wait_for_sync(conn, foundSyncInstanceName, extraSpecs)
|
|
|
|
return foundSyncInstanceName
|
|
|
|
def get_firmware_version(self, conn, arrayName):
|
|
"""Get the firmware version of array.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param arrayName: the array name
|
|
:returns: string -- firmwareVersion
|
|
"""
|
|
firmwareVersion = None
|
|
softwareIdentities = conn.EnumerateInstanceNames(
|
|
'symm_storageSystemsoftwareidentity')
|
|
|
|
for softwareIdentity in softwareIdentities:
|
|
if arrayName in softwareIdentity['InstanceID']:
|
|
softwareIdentityInstance = conn.GetInstance(softwareIdentity)
|
|
propertiesList = softwareIdentityInstance.properties.items()
|
|
for properties in propertiesList:
|
|
if properties[0] == 'VersionString':
|
|
cimProperties = properties[1]
|
|
firmwareVersion = cimProperties.value
|
|
break
|
|
|
|
return firmwareVersion
|
|
|
|
def get_srp_pool_stats(self, conn, arrayName, poolName):
|
|
"""Get the totalManagedSpace, remainingManagedSpace.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param arrayName: the array name
|
|
:param poolName: the pool name
|
|
:returns: totalCapacityGb
|
|
:returns: remainingCapacityGb
|
|
"""
|
|
totalCapacityGb = -1
|
|
remainingCapacityGb = -1
|
|
storageSystemInstanceName = self.find_storageSystem(conn, arrayName)
|
|
|
|
srpPoolInstanceNames = conn.AssociatorNames(
|
|
storageSystemInstanceName,
|
|
ResultClass='Symm_SRPStoragePool')
|
|
|
|
for srpPoolInstanceName in srpPoolInstanceNames:
|
|
poolInstanceID = srpPoolInstanceName['InstanceID']
|
|
poolnameStr, _systemName = (
|
|
self.parse_pool_instance_id_v3(poolInstanceID))
|
|
|
|
if six.text_type(poolName) == six.text_type(poolnameStr):
|
|
try:
|
|
# Check that pool hasn't suddenly 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.convert_bits_to_gbs(
|
|
totalManagedSpace)
|
|
elif properties[0] == 'RemainingManagedSpace':
|
|
cimProperties = properties[1]
|
|
remainingManagedSpace = cimProperties.value
|
|
remainingCapacityGb = self.convert_bits_to_gbs(
|
|
remainingManagedSpace)
|
|
except Exception:
|
|
pass
|
|
|
|
return totalCapacityGb, remainingCapacityGb
|
|
|
|
def isArrayV3(self, conn, arrayName):
|
|
"""Check if the array is V2 or V3.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param arrayName: the array name
|
|
:returns: boolean
|
|
"""
|
|
firmwareVersion = self.get_firmware_version(conn, arrayName)
|
|
|
|
m = re.search('^(\d+)', firmwareVersion)
|
|
majorVersion = m.group(0)
|
|
|
|
if int(majorVersion) >= 5900:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def get_pool_and_system_name_v2(
|
|
self, conn, storageSystemInstanceName, poolNameInStr):
|
|
"""Get pool instance and system name string for V2.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param storageSystemInstanceName: the storage system instance name
|
|
:param poolNameInStr: the pool name
|
|
:returns: foundPoolInstanceName
|
|
:returns: string -- systemNameStr
|
|
"""
|
|
vpoolInstanceNames = conn.AssociatorNames(
|
|
storageSystemInstanceName,
|
|
ResultClass='EMC_VirtualProvisioningPool')
|
|
|
|
return self._get_pool_instance_and_system_name(
|
|
conn, vpoolInstanceNames, storageSystemInstanceName,
|
|
poolNameInStr)
|
|
|
|
def get_pool_and_system_name_v3(
|
|
self, conn, storageSystemInstanceName, poolNameInStr):
|
|
"""Get pool instance and system name string for V2.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param storageSystemInstanceName: the storage system instance name
|
|
:param poolNameInStr: the pool name
|
|
:returns: foundPoolInstanceName
|
|
:returns: string -- systemNameStr
|
|
"""
|
|
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 poolname: pool name (string)
|
|
:returns: foundPoolInstanceName, systemname
|
|
"""
|
|
foundPoolInstanceName = None
|
|
poolnameStr = None
|
|
systemNameStr = storageSystemInstanceName['Name']
|
|
for poolInstanceName in poolInstanceNames:
|
|
# 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):
|
|
try:
|
|
conn.GetInstance(poolInstanceName)
|
|
foundPoolInstanceName = poolInstanceName
|
|
except Exception:
|
|
foundPoolInstanceName = None
|
|
break
|
|
|
|
return foundPoolInstanceName, systemNameStr
|
|
|
|
def get_pool_name(self, conn, poolInstanceName):
|
|
"""Get the pool name from the instance
|
|
|
|
:param conn: the ecom connection
|
|
:param poolInstanceName: the pool instance
|
|
:returns: poolnameStr
|
|
"""
|
|
poolnameStr = None
|
|
try:
|
|
poolInstance = conn.GetInstance(poolInstanceName)
|
|
poolnameStr = poolInstance['ElementName']
|
|
except Exception:
|
|
pass
|
|
return poolnameStr
|
|
|
|
def find_storageSystem(self, conn, arrayStr):
|
|
"""Find an array instance name by the array name.
|
|
|
|
:param conn: the ecom connection
|
|
:param arrayStr: the array Serial number (string)
|
|
:returns: foundPoolInstanceName, the CIM Instance Name of the Pool
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
foundStorageSystemInstanceName = None
|
|
storageSystemInstanceNames = conn.EnumerateInstanceNames(
|
|
'EMC_StorageSystem')
|
|
for storageSystemInstanceName in storageSystemInstanceNames:
|
|
arrayName = storageSystemInstanceName['Name']
|
|
index = arrayName.find(arrayStr)
|
|
if index > -1:
|
|
foundStorageSystemInstanceName = storageSystemInstanceName
|
|
|
|
if foundStorageSystemInstanceName is None:
|
|
exceptionMessage = (_("StorageSystem %(array)s is not found.")
|
|
% {'array': arrayStr})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
|
|
|
LOG.debug("Array Found: %(array)s.",
|
|
{'array': arrayStr})
|
|
|
|
return foundStorageSystemInstanceName
|
|
|
|
def is_in_range(self, volumeSize, maximumVolumeSize, minimumVolumeSize):
|
|
"""Check that volumeSize is in range.
|
|
|
|
:param volumeSize: volume size
|
|
:param maximumVolumeSize: the max volume size
|
|
:param minimumVolumeSize: the min volume size
|
|
:returns: boolean
|
|
"""
|
|
|
|
if (long(volumeSize) < long(maximumVolumeSize)) and (
|
|
long(volumeSize) >= long(minimumVolumeSize)):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def verify_slo_workload(self, slo, workload):
|
|
"""Check if SLO and workload values are valid.
|
|
|
|
:param slo: Service Level Object e.g bronze
|
|
:param workload: workload e.g DSS
|
|
:returns: boolean
|
|
"""
|
|
isValidSLO = False
|
|
isValidWorkload = False
|
|
|
|
if not slo:
|
|
isValidSLO = True
|
|
if not workload:
|
|
isValidWorkload = True
|
|
|
|
validSLOs = ['Bronze', 'Silver', 'Gold',
|
|
'Platinum', 'Diamond', 'Optimized',
|
|
'NONE']
|
|
validWorkloads = ['DSS_REP', 'DSS', 'OLTP',
|
|
'OLTP_REP', 'NONE']
|
|
|
|
for validSLO in validSLOs:
|
|
if slo == validSLO:
|
|
isValidSLO = True
|
|
break
|
|
|
|
for validWorkload in validWorkloads:
|
|
if workload == validWorkload:
|
|
isValidWorkload = True
|
|
break
|
|
|
|
if not isValidSLO:
|
|
LOG.error(_LE(
|
|
"SLO: %(slo)s is not valid. Valid values are Bronze, Silver, "
|
|
"Gold, Platinum, Diamond, Optimized, NONE."), {'slo': slo})
|
|
|
|
if not isValidWorkload:
|
|
LOG.error(_LE(
|
|
"Workload: %(workload)s is not valid. Valid values are "
|
|
"DSS_REP, DSS, OLTP, OLTP_REP, NONE."), {'workload': workload})
|
|
|
|
return isValidSLO, isValidWorkload
|
|
|
|
def get_v3_storage_group_name(self, poolName, slo, workload):
|
|
"""Determine default v3 storage group from extraSpecs.
|
|
|
|
:param poolName: the poolName
|
|
:param slo: the SLO string e.g Bronze
|
|
:param workload: the workload string e.g DSS
|
|
:returns: storageGroupName
|
|
"""
|
|
if slo and workload:
|
|
storageGroupName = ("OS-%(poolName)s-%(slo)s-%(workload)s-SG"
|
|
% {'poolName': poolName,
|
|
'slo': slo,
|
|
'workload': workload})
|
|
else:
|
|
storageGroupName = ("OS-no_SLO-SG")
|
|
return storageGroupName
|
|
|
|
def _get_fast_settings_from_storage_group(self, storageGroupInstance):
|
|
"""Get the emc FAST setting from the storage group.
|
|
|
|
:param storageGroupInstance: the storage group instance
|
|
:returns: emcFastSetting
|
|
"""
|
|
emcFastSetting = None
|
|
propertiesList = storageGroupInstance.properties.items()
|
|
for properties in propertiesList:
|
|
if properties[0] == 'EMCFastSetting':
|
|
cimProperties = properties[1]
|
|
emcFastSetting = cimProperties.value
|
|
break
|
|
return emcFastSetting
|
|
|
|
def get_volume_meta_head(self, conn, volumeInstanceName):
|
|
"""Get the head of a meta volume.
|
|
|
|
:param conn: the ecom connection
|
|
:param volumeInstanceName: the composite volume instance name
|
|
:returns: the instance name of the meta volume head
|
|
"""
|
|
metaHeadInstanceName = None
|
|
metaHeads = conn.AssociatorNames(
|
|
volumeInstanceName,
|
|
ResultClass='EMC_Meta')
|
|
|
|
if len(metaHeads) > 0:
|
|
metaHeadInstanceName = metaHeads[0]
|
|
if metaHeadInstanceName is None:
|
|
LOG.info(_LI(
|
|
"Volume %(volume)s does not have meta device members."),
|
|
{'volume': volumeInstanceName})
|
|
|
|
return metaHeadInstanceName
|
|
|
|
def get_meta_members_of_composite_volume(
|
|
self, conn, metaHeadInstanceName):
|
|
"""Get the member volumes of a composite volume.
|
|
|
|
:param conn: the ecom connection
|
|
:param metaHeadInstanceName: head of the composite volume
|
|
:returns: an array containing instance names of member volumes
|
|
"""
|
|
metaMembers = conn.AssociatorNames(
|
|
metaHeadInstanceName,
|
|
AssocClass='CIM_BasedOn',
|
|
ResultClass='EMC_PartialAllocOfConcreteExtent')
|
|
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.
|
|
|
|
: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)
|
|
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
|
|
|
|
def get_existing_instance(self, conn, instanceName):
|
|
"""Check that the instance name still exists and return the instance.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param instanceName: the instanceName to be checked
|
|
:returns: instance or None
|
|
"""
|
|
instance = None
|
|
try:
|
|
instance = conn.GetInstance(instanceName, LocalOnly=False)
|
|
except pywbem.cim_operations.CIMError as arg:
|
|
instance = self.process_exception_args(arg, instanceName)
|
|
return instance
|
|
|
|
def process_exception_args(self, arg, instanceName):
|
|
"""Process exception arguments.
|
|
|
|
:param arg: the arg list
|
|
:param instanceName: the instance name
|
|
:returns: None
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
instance = None
|
|
code, desc = arg[0], arg[1]
|
|
if code == CIM_ERR_NOT_FOUND:
|
|
# Object doesn't exist any more.
|
|
instance = None
|
|
else:
|
|
# Something else that we cannot recover from has happened.
|
|
LOG.error(_LE("Exception: %s"), six.text_type(desc))
|
|
exceptionMessage = (_(
|
|
"Cannot verify the existence of object:"
|
|
"%(instanceName)s.")
|
|
% {'instanceName': instanceName})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exceptionMessage)
|
|
return instance
|
|
|
|
def find_replication_service_capabilities(self, conn, storageSystemName):
|
|
"""Find the replication service capabilities instance name.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param storageSystemName: the storage system name
|
|
:returns: foundRepServCapability
|
|
"""
|
|
foundRepServCapability = None
|
|
repservices = conn.EnumerateInstanceNames(
|
|
'CIM_ReplicationServiceCapabilities')
|
|
for repservCap in repservices:
|
|
if storageSystemName in repservCap['InstanceID']:
|
|
foundRepServCapability = repservCap
|
|
LOG.debug("Found Replication Service Capabilities: "
|
|
"%(repservCap)s",
|
|
{'repservCap': repservCap})
|
|
break
|
|
if foundRepServCapability is None:
|
|
exceptionMessage = (_("Replication Service Capability not found "
|
|
"on %(storageSystemName)s.")
|
|
% {'storageSystemName': storageSystemName})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
|
|
|
return foundRepServCapability
|
|
|
|
def is_clone_licensed(self, conn, capabilityInstanceName, isV3):
|
|
"""Check if the clone feature is licensed and enabled.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param capabilityInstanceName: the replication service capabilities
|
|
instance name
|
|
:returns: True if licensed and enabled; False otherwise.
|
|
"""
|
|
capabilityInstance = conn.GetInstance(capabilityInstanceName)
|
|
propertiesList = capabilityInstance.properties.items()
|
|
for properties in propertiesList:
|
|
if properties[0] == 'SupportedReplicationTypes':
|
|
cimProperties = properties[1]
|
|
repTypes = cimProperties.value
|
|
LOG.debug("Found supported replication types: "
|
|
"%(repTypes)s",
|
|
{'repTypes': repTypes})
|
|
if isV3:
|
|
if (SYNC_SNAPSHOT_LOCAL in repTypes or
|
|
ASYNC_SNAPSHOT_LOCAL in repTypes):
|
|
# Snapshot is a supported replication type.
|
|
LOG.debug("Snapshot for VMAX3 is licensed and "
|
|
"enabled.")
|
|
return True
|
|
else:
|
|
if CLONE_REPLICATION_TYPE in repTypes:
|
|
# Clone is a supported replication type.
|
|
LOG.debug("Clone for VMAX2 is licensed and "
|
|
"enabled.")
|
|
return True
|
|
return False
|
|
|
|
def create_storage_hardwareId_instance_name(
|
|
self, conn, hardwareIdManagementService, initiator):
|
|
"""Create storage hardware ID instance name based on the WWPN/IQN.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param hardwareIdManagementService: the hardware ID management service
|
|
:param initiator: initiator(IQN or WWPN) to create the hardware ID
|
|
instance
|
|
:returns: hardwareIdList
|
|
"""
|
|
hardwareIdList = None
|
|
hardwareIdType = self._get_hardware_type(initiator)
|
|
rc, ret = conn.InvokeMethod(
|
|
'CreateStorageHardwareID',
|
|
hardwareIdManagementService,
|
|
StorageID=initiator,
|
|
IDType=self.get_num(hardwareIdType, '16'))
|
|
|
|
if 'HardwareID' in ret:
|
|
LOG.debug("Created hardware ID instance for initiator:"
|
|
"%(initiator)s rc=%(rc)d, ret=%(ret)s",
|
|
{'initiator': initiator, 'rc': rc, 'ret': ret})
|
|
hardwareIdList = ret['HardwareID']
|
|
else:
|
|
LOG.warning(_LW("CreateStorageHardwareID failed. initiator: "
|
|
"%(initiator)s, rc=%(rc)d, ret=%(ret)s."),
|
|
{'initiator': initiator, 'rc': rc, 'ret': ret})
|
|
return hardwareIdList
|
|
|
|
def _get_hardware_type(
|
|
self, initiator):
|
|
"""Determine the hardware type based on the initiator.
|
|
|
|
:param initiator: initiator(IQN or WWPN)
|
|
:returns: hardwareTypeId
|
|
"""
|
|
hardwareTypeId = 0
|
|
try:
|
|
int(initiator, 16)
|
|
hardwareTypeId = 2
|
|
except Exception:
|
|
if 'iqn' in initiator.lower():
|
|
hardwareTypeId = 5
|
|
if hardwareTypeId == 0:
|
|
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, 'ServiceLevel')
|
|
kwargs['SLO'] = slo
|
|
workload = self._process_tag(element, 'Workload')
|
|
if workload is None and slo:
|
|
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 portGroupElement in portGroupElements:
|
|
if portGroupElement.childNodes:
|
|
portGroupName = portGroupElement.childNodes[0].nodeValue
|
|
if portGroupName:
|
|
portGroupNames.append(portGroupName.strip())
|
|
portGroupNames = EMCVMAXUtils._filter_list(portGroupNames)
|
|
if len(portGroupNames) > 0:
|
|
return EMCVMAXUtils._get_random_pg_from_list(portGroupNames)
|
|
|
|
exception_message = (_("No Port Group elements found in config file."))
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
@staticmethod
|
|
def _get_random_pg_from_list(portgroupnames):
|
|
"""From list of portgroup, choose one randomly
|
|
|
|
:param portGroupNames: list of available portgroups
|
|
:returns: portGroupName - the random portgroup
|
|
"""
|
|
portgroupname = (
|
|
portgroupnames[random.randint(0, len(portgroupnames) - 1)])
|
|
|
|
return portgroupname
|
|
|
|
@staticmethod
|
|
def _filter_list(portgroupnames):
|
|
"""Clean up the port group list
|
|
|
|
:param portgroupnames: list of available portgroups
|
|
:returns: portgroupnames - cleaned up list
|
|
"""
|
|
portgroupnames = filter(None, portgroupnames)
|
|
# Convert list to set to remove duplicate portgroups
|
|
portgroupnames = list(set(portgroupnames))
|
|
return portgroupnames
|
|
|
|
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.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storageSystem: the storage system name
|
|
:param deviceID: string value of the volume device ID
|
|
:returns: foundVolumeInstanceName
|
|
"""
|
|
foundVolumeInstanceName = None
|
|
volumeInstanceNames = conn.EnumerateInstanceNames(
|
|
'CIM_StorageVolume')
|
|
for volumeInstanceName in volumeInstanceNames:
|
|
if storageSystem not in volumeInstanceName['SystemName']:
|
|
continue
|
|
if deviceID == volumeInstanceName['DeviceID']:
|
|
foundVolumeInstanceName = volumeInstanceName
|
|
LOG.debug("Found volume: %(vol)s",
|
|
{'vol': foundVolumeInstanceName})
|
|
break
|
|
if foundVolumeInstanceName is None:
|
|
exceptionMessage = (_("Volume %(deviceID)s not found.")
|
|
% {'deviceID': deviceID})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
|
|
|
return foundVolumeInstanceName
|
|
|
|
def get_volume_element_name(self, volumeId):
|
|
"""Get volume element name follows naming convention, i.e. 'OS-UUID'.
|
|
|
|
:param volumeId: volume id containing uuid
|
|
:returns: volume element name in format of OS-UUID
|
|
"""
|
|
elementName = volumeId
|
|
uuid_regex = (re.compile(
|
|
'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}',
|
|
re.I))
|
|
match = uuid_regex.search(volumeId)
|
|
if match:
|
|
volumeUUID = match.group()
|
|
elementName = ("%(prefix)s%(volumeUUID)s"
|
|
% {'prefix': VOLUME_ELEMENT_NAME_PREFIX,
|
|
'volumeUUID': volumeUUID})
|
|
LOG.debug(
|
|
"get_volume_element_name elementName: %(elementName)s.",
|
|
{'elementName': elementName})
|
|
return elementName
|
|
|
|
def rename_volume(self, conn, volume, newName):
|
|
"""Change the volume ElementName to specified new name.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param volume: the volume instance name or volume instance
|
|
:param newName: new ElementName of the volume
|
|
:returns: volumeInstance after rename
|
|
"""
|
|
if type(volume) is pywbem.cim_obj.CIMInstance:
|
|
volumeInstance = volume
|
|
else:
|
|
volumeInstance = conn.GetInstance(volume)
|
|
volumeInstance['ElementName'] = newName
|
|
|
|
LOG.debug("Rename volume to new ElementName %(newName)s.",
|
|
{'newName': newName})
|
|
|
|
conn.ModifyInstance(volumeInstance, PropertyList=['ElementName'])
|
|
|
|
return volumeInstance
|
|
|
|
def get_array_and_device_id(self, volume, external_ref):
|
|
"""Helper function for manage volume to get array name and device ID.
|
|
|
|
:param volume: volume object from API
|
|
:param external_ref: the existing volume object to be manged
|
|
:returns: string value of the array name and device ID
|
|
"""
|
|
deviceId = external_ref.get(u'source-name', None)
|
|
arrayName = ''
|
|
for metadata in volume['volume_metadata']:
|
|
if metadata['key'].lower() == 'array':
|
|
arrayName = metadata['value']
|
|
break
|
|
|
|
if deviceId:
|
|
LOG.debug("Get device ID of existing volume - device ID: "
|
|
"%(deviceId)s, Array: %(arrayName)s.",
|
|
{'deviceId': deviceId,
|
|
'arrayName': arrayName})
|
|
else:
|
|
exception_message = (_("Source volume device ID is required."))
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
return (arrayName, deviceId)
|
|
|
|
def get_associated_replication_from_source_volume(
|
|
self, conn, storageSystem, sourceDeviceId):
|
|
"""Get associated replication from source volume.
|
|
|
|
Given the source volume device ID, find associated replication
|
|
storage synchronized instance names.
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storageSystem: the storage system name
|
|
:param source: target volume object
|
|
:returns: foundSyncName (String)
|
|
"""
|
|
foundSyncInstanceName = None
|
|
syncInstanceNames = conn.EnumerateInstanceNames(
|
|
'SE_StorageSynchronized_SV_SV')
|
|
for syncInstanceName in syncInstanceNames:
|
|
sourceVolume = syncInstanceName['SystemElement']
|
|
if storageSystem != sourceVolume['SystemName']:
|
|
continue
|
|
if sourceVolume['DeviceID'] == sourceDeviceId:
|
|
# Check that it hasn't recently been deleted.
|
|
try:
|
|
conn.GetInstance(syncInstanceName)
|
|
foundSyncInstanceName = syncInstanceName
|
|
LOG.debug("Found sync Name: "
|
|
"%(syncName)s.",
|
|
{'syncName': foundSyncInstanceName})
|
|
except Exception:
|
|
foundSyncInstanceName = None
|
|
break
|
|
|
|
if foundSyncInstanceName is None:
|
|
LOG.info(_LI(
|
|
"No replication synchronization session found associated "
|
|
"with source volume %(source)s on %(storageSystem)s."),
|
|
{'source': sourceDeviceId, 'storageSystem': storageSystem})
|
|
|
|
return foundSyncInstanceName
|
|
|
|
def get_volume_model_updates(
|
|
self, volumes, cgId, status='available'):
|
|
"""Update the volume model's status and return it.
|
|
|
|
:param volumes: volumes object api
|
|
:param cgId: cg id
|
|
:param status: string value reflects the status of the member volume
|
|
:returns: volume_model_updates - updated volumes
|
|
"""
|
|
volume_model_updates = []
|
|
LOG.info(_LI(
|
|
"Updating status for CG: %(id)s."),
|
|
{'id': cgId})
|
|
if volumes:
|
|
for volume in volumes:
|
|
volume_model_updates.append({'id': volume['id'],
|
|
'status': status})
|
|
else:
|
|
LOG.info(_LI("No volume found for CG: %(cg)s."),
|
|
{'cg': cgId})
|
|
return volume_model_updates
|
|
|
|
def get_smi_version(self, conn):
|
|
"""Get the SMI_S version.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:returns: string -- version
|
|
"""
|
|
intVersion = 0
|
|
swIndentityInstances = conn.EnumerateInstances(
|
|
'SE_ManagementServerSoftwareIdentity')
|
|
if swIndentityInstances:
|
|
swIndentityInstance = swIndentityInstances[0]
|
|
majorVersion = swIndentityInstance['MajorVersion']
|
|
minorVersion = swIndentityInstance['MinorVersion']
|
|
revisionNumber = swIndentityInstance['RevisionNumber']
|
|
|
|
intVersion = int(six.text_type(majorVersion) +
|
|
six.text_type(minorVersion) +
|
|
six.text_type(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
|
|
|
|
def generate_unique_trunc_host(self, hostName):
|
|
"""Create a unique short host name under 40 chars
|
|
|
|
:param sgName: long storage group name
|
|
:returns: truncated storage group name
|
|
"""
|
|
if hostName and len(hostName) > 38:
|
|
hostName = hostName.lower()
|
|
m = hashlib.md5()
|
|
m.update(hostName.encode('utf-8'))
|
|
uuid = m.hexdigest()
|
|
return(
|
|
("%(host)s%(uuid)s"
|
|
% {'host': hostName[-6:],
|
|
'uuid': uuid}))
|
|
else:
|
|
return hostName
|
|
|
|
def generate_unique_trunc_pool(self, poolName):
|
|
"""Create a unique pool name under 16 chars
|
|
|
|
:param poolName: long pool name
|
|
:returns: truncated pool name
|
|
"""
|
|
if poolName and len(poolName) > MAX_POOL_LENGTH:
|
|
return (
|
|
("%(first)s_%(last)s"
|
|
% {'first': poolName[:8],
|
|
'last': poolName[-7:]}))
|
|
else:
|
|
return poolName
|
|
|
|
def generate_unique_trunc_fastpolicy(self, fastPolicyName):
|
|
"""Create a unique fast policy name under 14 chars
|
|
|
|
:param fastPolicyName: long fast policy name
|
|
:returns: truncated fast policy name
|
|
"""
|
|
if fastPolicyName and len(fastPolicyName) > MAX_FASTPOLICY_LENGTH:
|
|
return (
|
|
("%(first)s_%(last)s"
|
|
% {'first': fastPolicyName[:7],
|
|
'last': fastPolicyName[-6:]}))
|
|
else:
|
|
return fastPolicyName
|
|
|
|
def get_iscsi_protocol_endpoints(self, conn, portgroupinstancename):
|
|
"""Get the iscsi protocol endpoints of a port group.
|
|
|
|
:param conn: the ecom connection
|
|
:param portgroupinstancename: the portgroup instance name
|
|
:returns: iscsiendpoints
|
|
"""
|
|
iscsiendpoints = conn.AssociatorNames(
|
|
portgroupinstancename,
|
|
AssocClass='CIM_MemberOfCollection')
|
|
return iscsiendpoints
|
|
|
|
def get_tcp_protocol_endpoints(self, conn, iscsiendpointinstancename):
|
|
"""Get the tcp protocol endpoints associated with an iscsi endpoint
|
|
|
|
:param conn: the ecom connection
|
|
:param iscsiendpointinstancename: the iscsi endpoint instance name
|
|
:returns: tcpendpoints
|
|
"""
|
|
tcpendpoints = conn.AssociatorNames(
|
|
iscsiendpointinstancename,
|
|
AssocClass='CIM_BindsTo')
|
|
return tcpendpoints
|
|
|
|
def get_ip_protocol_endpoints(self, conn, tcpendpointinstancename):
|
|
"""Get the ip protocol endpoints associated with an tcp endpoint
|
|
|
|
:param conn: the ecom connection
|
|
:param tcpendpointinstancename: the tcp endpoint instance name
|
|
:returns: ipendpoints
|
|
"""
|
|
ipendpoints = conn.AssociatorNames(
|
|
tcpendpointinstancename,
|
|
AssocClass='CIM_BindsTo')
|
|
return ipendpoints
|
|
|
|
def get_iscsi_ip_address(self, conn, ipendpointinstancename):
|
|
"""Get the IPv4Address from the ip endpoint instance name
|
|
|
|
:param conn: the ecom connection
|
|
:param ipendpointinstancename: the ip endpoint instance name
|
|
:returns: foundIpAddress
|
|
"""
|
|
foundIpAddress = None
|
|
ipendpointinstance = conn.GetInstance(ipendpointinstancename)
|
|
propertiesList = ipendpointinstance.properties.items()
|
|
for properties in propertiesList:
|
|
if properties[0] == 'IPv4Address':
|
|
cimProperties = properties[1]
|
|
foundIpAddress = cimProperties.value
|
|
return foundIpAddress
|
|
|
|
def get_target_endpoints(self, conn, hardwareId):
|
|
"""Given the hardwareId get the target endpoints.
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param hardwareId: the hardware Id
|
|
:returns: targetEndpoints
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
protocolControllerInstanceName = self.get_protocol_controller(
|
|
conn, hardwareId)
|
|
|
|
targetEndpoints = conn.AssociatorNames(
|
|
protocolControllerInstanceName,
|
|
ResultClass='EMC_FCSCSIProtocolEndpoint')
|
|
|
|
return targetEndpoints
|
|
|
|
def get_protocol_controller(self, conn, hardwareinstancename):
|
|
"""Get the front end protocol endpoints of a hardware instance
|
|
|
|
:param conn: the ecom connection
|
|
:param hardwareinstancename: the hardware instance name
|
|
:returns: protocolControllerInstanceName
|
|
:raises: VolumeBackendAPIException
|
|
"""
|
|
protocolControllerInstanceName = None
|
|
protocol_controllers = conn.AssociatorNames(
|
|
hardwareinstancename,
|
|
ResultClass='EMC_FrontEndSCSIProtocolController')
|
|
if len(protocol_controllers) > 0:
|
|
protocolControllerInstanceName = protocol_controllers[0]
|
|
if protocolControllerInstanceName is None:
|
|
exceptionMessage = (_(
|
|
"Unable to get target endpoints for hardwareId "
|
|
"%(hardwareIdInstance)s.")
|
|
% {'hardwareIdInstance': hardwareinstancename})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
|
return protocolControllerInstanceName
|
|
|
|
def get_replication_setting_data(self, conn, repServiceInstanceName,
|
|
replication_type, extraSpecs):
|
|
"""Get the replication setting data
|
|
|
|
:param conn: connection the ecom server
|
|
:param repServiceInstanceName: the storage group instance name
|
|
:param replication_type: the replication type
|
|
:param copy_methodology: the copy methodology
|
|
:returns: instance rsdInstance
|
|
"""
|
|
repServiceCapabilityInstanceNames = conn.AssociatorNames(
|
|
repServiceInstanceName,
|
|
ResultClass='CIM_ReplicationServiceCapabilities',
|
|
AssocClass='CIM_ElementCapabilities')
|
|
repServiceCapabilityInstanceName = (
|
|
repServiceCapabilityInstanceNames[0])
|
|
|
|
rc, rsd = conn.InvokeMethod(
|
|
'GetDefaultReplicationSettingData',
|
|
repServiceCapabilityInstanceName,
|
|
ReplicationType=self.get_num(replication_type, '16'))
|
|
|
|
if rc != 0:
|
|
rc, errordesc = self.wait_for_job_complete(conn, rsd,
|
|
extraSpecs)
|
|
if rc != 0:
|
|
exceptionMessage = (_(
|
|
"Error getting ReplicationSettingData. "
|
|
"Return code: %(rc)lu. "
|
|
"Error: %(error)s.")
|
|
% {'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exceptionMessage)
|
|
return rsd
|
|
|
|
def set_copy_methodology_in_rsd(self, conn, repServiceInstanceName,
|
|
replication_type, copy_methodology,
|
|
extraSpecs):
|
|
"""Get the replication setting data
|
|
|
|
:param conn: connection the ecom server
|
|
:param repServiceInstanceName: the storage group instance name
|
|
:param replication_type: the replication type
|
|
:param copy_methodology: the copy methodology
|
|
:returns: instance rsdInstance
|
|
"""
|
|
rsd = self.get_replication_setting_data(
|
|
conn, repServiceInstanceName, replication_type, extraSpecs)
|
|
rsdInstance = rsd['DefaultInstance']
|
|
rsdInstance['DesiredCopyMethodology'] = (
|
|
self.get_num(copy_methodology, '16'))
|
|
return rsdInstance
|
|
|
|
def set_target_element_supplier_in_rsd(
|
|
self, conn, repServiceInstanceName, replication_type,
|
|
target_type, extraSpecs):
|
|
"""Get the replication setting data
|
|
|
|
:param conn: connection the ecom server
|
|
:param repServiceInstanceName: the storage group instance name
|
|
:param replication_type: the replication type
|
|
:param target_type: Use existing, Create new, Use and create
|
|
:returns: instance rsdInstance
|
|
"""
|
|
rsd = self.get_replication_setting_data(
|
|
conn, repServiceInstanceName, replication_type, extraSpecs)
|
|
rsdInstance = rsd['DefaultInstance']
|
|
rsdInstance['TargetElementSupplier'] = (
|
|
self.get_num(target_type, '16'))
|
|
|
|
return rsdInstance
|
|
|
|
def get_v3_default_sg_instance_name(
|
|
self, conn, poolName, slo, workload, storageSystemName):
|
|
"""Get the V3 default instance name
|
|
|
|
:param conn: the connection to the ecom server
|
|
:param poolName: the pool name
|
|
:param slo: the SLO
|
|
:param workload: the workload
|
|
:param storageSystemName: the storage system name
|
|
:returns: the storage group instance name
|
|
"""
|
|
storageGroupName = self.get_v3_storage_group_name(
|
|
poolName, slo, workload)
|
|
controllerConfigService = (
|
|
self.find_controller_configuration_service(
|
|
conn, storageSystemName))
|
|
sgInstanceName = self.find_storage_masking_group(
|
|
conn, controllerConfigService, storageGroupName)
|
|
return storageGroupName, controllerConfigService, sgInstanceName
|
|
|
|
def get_ratio_from_max_sub_per(self, max_subscription_percent):
|
|
"""Get ratio from max subscription percent if it exists.
|
|
|
|
Check if the max subscription is set on the pool, if it is convert
|
|
it to a ratio.
|
|
|
|
:param max_subscription_percent: max subscription percent
|
|
:returns: max_over_subscription_ratio
|
|
"""
|
|
if max_subscription_percent == '0':
|
|
return None
|
|
try:
|
|
max_subscription_percent_int = int(max_subscription_percent)
|
|
except ValueError:
|
|
LOG.error(_LE("Cannot convert max subscription percent to int."))
|
|
return None
|
|
return float(max_subscription_percent_int) / 100
|
|
|
|
def override_ratio(self, max_over_sub_ratio, max_sub_ratio_from_per):
|
|
"""Override ratio if necessary
|
|
|
|
The over subscription ratio will be overridden if the max subscription
|
|
percent is less than the user supplied max oversubscription ratio.
|
|
|
|
:param max_over_sub_ratio: user supplied over subscription ratio
|
|
:param max_sub_ratio_from_per: property on the pool
|
|
:returns: max_over_sub_ratio
|
|
"""
|
|
if max_over_sub_ratio:
|
|
try:
|
|
max_over_sub_ratio = max(float(max_over_sub_ratio),
|
|
float(max_sub_ratio_from_per))
|
|
except ValueError:
|
|
max_over_sub_ratio = float(max_sub_ratio_from_per)
|
|
elif max_sub_ratio_from_per:
|
|
max_over_sub_ratio = float(max_sub_ratio_from_per)
|
|
|
|
return max_over_sub_ratio
|
|
|
|
def update_storagegroup_qos(self, conn, storagegroup, extraspecs):
|
|
"""Update the storagegroupinstance with qos details.
|
|
|
|
If MaxIOPS or maxMBPS is in extraspecs, then DistributionType can be
|
|
modified in addition to MaxIOPS or/and maxMBPS
|
|
If MaxIOPS or maxMBPS is NOT in extraspecs, we check to see if
|
|
either is set in StorageGroup. If so, then DistributionType can be
|
|
modified
|
|
|
|
:param conn: connection to the ecom server
|
|
:param storagegroup: the storagegroup instance name
|
|
:param extraSpecs: extra specifications
|
|
"""
|
|
if type(storagegroup) is pywbem.cim_obj.CIMInstance:
|
|
storagegroupInstance = storagegroup
|
|
else:
|
|
storagegroupInstance = conn.GetInstance(storagegroup)
|
|
propertylist = []
|
|
if 'maxIOPS' in extraspecs.get('qos'):
|
|
maxiops = self.get_num(extraspecs.get('qos').get('maxIOPS'), '32')
|
|
if maxiops != storagegroupInstance['EMCMaximumIO']:
|
|
storagegroupInstance['EMCMaximumIO'] = maxiops
|
|
propertylist.append('EMCMaximumIO')
|
|
if 'maxMBPS' in extraspecs.get('qos'):
|
|
maxmbps = self.get_num(extraspecs.get('qos').get('maxMBPS'), '32')
|
|
if maxmbps != storagegroupInstance['EMCMaximumBandwidth']:
|
|
storagegroupInstance['EMCMaximumBandwidth'] = maxmbps
|
|
propertylist.append('EMCMaximumBandwidth')
|
|
if 'DistributionType' in extraspecs.get('qos') and (
|
|
propertylist or (
|
|
storagegroupInstance['EMCMaximumBandwidth'] != 0) or (
|
|
storagegroupInstance['EMCMaximumIO'] != 0)):
|
|
dynamicdict = {'never': 1, 'onfailure': 2, 'always': 3}
|
|
dynamicvalue = dynamicdict.get(
|
|
extraspecs.get('qos').get('DistributionType').lower())
|
|
if dynamicvalue:
|
|
distributiontype = self.get_num(dynamicvalue, '16')
|
|
if distributiontype != (
|
|
storagegroupInstance['EMCMaxIODynamicDistributionType']
|
|
):
|
|
storagegroupInstance['EMCMaxIODynamicDistributionType'] = (
|
|
distributiontype)
|
|
propertylist.append('EMCMaxIODynamicDistributionType')
|
|
if propertylist:
|
|
modifiedInstance = conn.ModifyInstance(storagegroupInstance,
|
|
PropertyList=propertylist)
|
|
return modifiedInstance
|
|
|
|
def insert_live_migration_record(self, volume, maskingviewdict,
|
|
connector, extraSpecs):
|
|
"""Insert a record of live migration destination into a temporary file
|
|
|
|
:param volume: the volume dictionary
|
|
:param maskingviewdict: the storage group instance name
|
|
:param connector: the connector Object
|
|
:param extraSpecs: the extraSpecs dict
|
|
"""
|
|
live_migration_details = self.get_live_migration_record(volume, True)
|
|
if live_migration_details:
|
|
if volume['id'] not in live_migration_details:
|
|
live_migration_details[volume['id']] = [maskingviewdict,
|
|
connector, extraSpecs]
|
|
else:
|
|
live_migration_details = {volume['id']: [maskingviewdict,
|
|
connector, extraSpecs]}
|
|
try:
|
|
with open(LIVE_MIGRATION_FILE, "wb") as f:
|
|
pickle.dump(live_migration_details, f)
|
|
except Exception:
|
|
exceptionMessage = (_(
|
|
"Error in processing live migration file."))
|
|
LOG.exception(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exceptionMessage)
|
|
|
|
def delete_live_migration_record(self, volume):
|
|
"""Delete record of live migration
|
|
|
|
Delete record of live migration destination from file and if
|
|
after deletion of record, delete file if empty.
|
|
|
|
:param volume: the volume dictionary
|
|
"""
|
|
live_migration_details = self.get_live_migration_record(volume, True)
|
|
if live_migration_details:
|
|
if volume['id'] in live_migration_details:
|
|
del live_migration_details[volume['id']]
|
|
with open(LIVE_MIGRATION_FILE, "wb") as f:
|
|
pickle.dump(live_migration_details, f)
|
|
else:
|
|
LOG.debug("%(Volume)s doesn't exist in live migration "
|
|
"record.",
|
|
{'Volume': volume['id']})
|
|
if not live_migration_details:
|
|
os.remove(LIVE_MIGRATION_FILE)
|
|
|
|
def get_live_migration_record(self, volume, returnallrecords):
|
|
"""get record of live migration destination from a temporary file
|
|
|
|
:param volume: the volume dictionary
|
|
:param returnallrecords: if true, return all records in file
|
|
:returns: returns a single record or all records depending on
|
|
returnallrecords flag
|
|
"""
|
|
returned_record = None
|
|
if os.path.isfile(LIVE_MIGRATION_FILE):
|
|
with open(LIVE_MIGRATION_FILE, "rb") as f:
|
|
live_migration_details = pickle.load(f)
|
|
if returnallrecords:
|
|
returned_record = live_migration_details
|
|
else:
|
|
if volume['id'] in live_migration_details:
|
|
returned_record = live_migration_details[volume['id']]
|
|
else:
|
|
LOG.debug("%(Volume)s doesn't exist in live migration "
|
|
"record.",
|
|
{'Volume': volume['id']})
|
|
return returned_record
|
|
|
|
def get_iqn(self, conn, ipendpointinstancename):
|
|
"""Get the IPv4Address from the ip endpoint instance name.
|
|
|
|
:param conn: the ecom connection
|
|
:param ipendpointinstancename: the ip endpoint instance name
|
|
:returns: foundIqn
|
|
"""
|
|
foundIqn = None
|
|
ipendpointinstance = conn.GetInstance(ipendpointinstancename)
|
|
propertiesList = ipendpointinstance.properties.items()
|
|
for properties in propertiesList:
|
|
if properties[0] == 'Name':
|
|
cimProperties = properties[1]
|
|
foundIqn = cimProperties.value
|
|
return foundIqn
|
|
|
|
def check_ig_instance_name(
|
|
self, conn, initiatorGroupInstanceName):
|
|
"""Check if a given Initiator Group Instance Name has been deleted.
|
|
|
|
:param conn: the ecom connection
|
|
:param initiatorGroupInstanceName: the given IG instance name
|
|
:return: foundinitiatorGroupInstanceName or None if deleted
|
|
"""
|
|
foundinitiatorGroupInstanceName = self.get_existing_instance(
|
|
conn, initiatorGroupInstanceName)
|
|
if foundinitiatorGroupInstanceName is not None:
|
|
LOG.debug("Found initiator group name: "
|
|
"%(igName)s.",
|
|
{'igName': foundinitiatorGroupInstanceName})
|
|
else:
|
|
LOG.debug("Could not find initiator group name: "
|
|
"%(igName)s.",
|
|
{'igName': foundinitiatorGroupInstanceName})
|
|
return foundinitiatorGroupInstanceName
|