Merge "EMC VMAX Driver Juno Update"
This commit is contained in:
commit
44fc450a8b
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,767 @@
|
|||
# Copyright (c) 2012 - 2014 EMC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume.drivers.emc import emc_vmax_provision
|
||||
from cinder.volume.drivers.emc import emc_vmax_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SG_PREFIX = 'OS_default_'
|
||||
DEFAULT_SG_POSTFIX = '_SG'
|
||||
|
||||
|
||||
class EMCVMAXFast(object):
|
||||
"""FAST Class for SMI-S based EMC volume drivers.
|
||||
|
||||
This FAST class is for EMC volume drivers based on SMI-S.
|
||||
It supports VMAX arrays.
|
||||
"""
|
||||
def __init__(self, prtcl):
|
||||
self.protocol = prtcl
|
||||
self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl)
|
||||
self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl)
|
||||
|
||||
def _check_if_fast_supported(self, conn, storageSystemInstanceName):
|
||||
"""Check to see if fast is supported on the array.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param storageSystemInstanceName: the storage system Instance name
|
||||
"""
|
||||
|
||||
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
|
||||
conn, storageSystemInstanceName)
|
||||
isTieringPolicySupported = self.is_tiering_policy_enabled(
|
||||
conn, tierPolicyServiceInstanceName)
|
||||
if isTieringPolicySupported is None:
|
||||
errorMessage = (_("Cannot determine whether "
|
||||
"Tiering Policy is support on this array."))
|
||||
LOG.error(errorMessage)
|
||||
|
||||
if isTieringPolicySupported is False:
|
||||
errorMessage = (_("Tiering Policy is not "
|
||||
"supported on this array."))
|
||||
LOG.error(errorMessage)
|
||||
return isTieringPolicySupported
|
||||
|
||||
def is_tiering_policy_enabled(self, conn, tierPolicyServiceInstanceName):
|
||||
"""Checks to see if tiering policy is supported.
|
||||
|
||||
We will only check if there is a fast policy specified in
|
||||
the config file.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyServiceInstanceName: the tier policy service
|
||||
instance name
|
||||
:returns: foundIsSupportsTieringPolicies - True/False
|
||||
"""
|
||||
foundIsSupportsTieringPolicies = None
|
||||
tierPolicyCapabilityInstanceNames = conn.AssociatorNames(
|
||||
tierPolicyServiceInstanceName,
|
||||
ResultClass='CIM_TierPolicyServiceCapabilities',
|
||||
AssocClass='CIM_ElementCapabilities')
|
||||
|
||||
tierPolicyCapabilityInstanceName = tierPolicyCapabilityInstanceNames[0]
|
||||
tierPolicyCapabilityInstance = conn.GetInstance(
|
||||
tierPolicyCapabilityInstanceName, LocalOnly=False)
|
||||
propertiesList = (tierPolicyCapabilityInstance
|
||||
.properties.items()) # ['SupportsTieringPolicies']
|
||||
for properties in propertiesList:
|
||||
if properties[0] == 'SupportsTieringPolicies':
|
||||
cimProperties = properties[1]
|
||||
foundIsSupportsTieringPolicies = cimProperties.value
|
||||
break
|
||||
|
||||
if foundIsSupportsTieringPolicies is None:
|
||||
exception_message = (_("Cannot determine if Tiering Policies "
|
||||
"are supported"))
|
||||
LOG.error(exception_message)
|
||||
|
||||
return foundIsSupportsTieringPolicies
|
||||
|
||||
def get_and_verify_default_storage_group(
|
||||
self, conn, controllerConfigService, volumeInstanceName,
|
||||
volumeName, fastPolicyName):
|
||||
"""Retrieves and verifies the default storage group for a volume.
|
||||
|
||||
Given the volumeInstanceName get any associated storage group and
|
||||
check that it is the default storage group. The default storage group
|
||||
should have been already created. If not found error is logged.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param controllerConfigService: the controller config service
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param volumeName: the volume name (String)
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:returns: foundDefaultStorageGroupInstanceName
|
||||
"""
|
||||
foundDefaultStorageGroupInstanceName = None
|
||||
storageSystemInstanceName = self.utils.find_storage_system(
|
||||
conn, controllerConfigService)
|
||||
|
||||
if not self._check_if_fast_supported(conn, storageSystemInstanceName):
|
||||
exceptionMessage = (_(
|
||||
"FAST is not supported on this array "))
|
||||
LOG.error(exceptionMessage)
|
||||
raise
|
||||
|
||||
assocStorageGroupInstanceName = (
|
||||
self.utils.get_storage_group_from_volume(conn, volumeInstanceName))
|
||||
defaultSgGroupName = (DEFAULT_SG_PREFIX + fastPolicyName +
|
||||
DEFAULT_SG_POSTFIX)
|
||||
defaultStorageGroupInstanceName = (
|
||||
self.utils.find_storage_masking_group(conn,
|
||||
controllerConfigService,
|
||||
defaultSgGroupName))
|
||||
if defaultStorageGroupInstanceName is None:
|
||||
exceptionMessage = (_(
|
||||
"Unable to find default storage group "
|
||||
"for FAST policy : %(fastPolicyName)s ")
|
||||
% {'fastPolicyName': fastPolicyName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise
|
||||
|
||||
if assocStorageGroupInstanceName == defaultStorageGroupInstanceName:
|
||||
foundDefaultStorageGroupInstanceName = (
|
||||
assocStorageGroupInstanceName)
|
||||
else:
|
||||
exceptionMessage = (_(
|
||||
"Volume: %(volumeName)s Does not belong "
|
||||
"to storage storage group %(defaultSgGroupName)s. ")
|
||||
% {'volumeName': volumeName,
|
||||
'defaultSgGroupName': defaultSgGroupName})
|
||||
LOG.warn(exceptionMessage)
|
||||
return foundDefaultStorageGroupInstanceName
|
||||
|
||||
def add_volume_to_default_storage_group_for_fast_policy(
|
||||
self, conn, controllerConfigService, volumeInstance,
|
||||
volumeName, fastPolicyName):
|
||||
"""Add a volume to the default storage group for FAST policy.
|
||||
|
||||
The storage group must pre-exist. Once added to the storage group,
|
||||
check the association to make sure it has been successfully added.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param volumeInstance: the volume instance
|
||||
:param volumeName: the volume name (String)
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:returns: assocStorageGroupInstanceName - the storage group
|
||||
associated with the volume
|
||||
"""
|
||||
failedRet = None
|
||||
defaultSgGroupName = (DEFAULT_SG_PREFIX + fastPolicyName +
|
||||
DEFAULT_SG_POSTFIX)
|
||||
storageGroupInstanceName = self.utils.find_storage_masking_group(
|
||||
conn, controllerConfigService, defaultSgGroupName)
|
||||
if storageGroupInstanceName is None:
|
||||
exceptionMessage = (_(
|
||||
"Unable to create default storage group for"
|
||||
" FAST policy : %(fastPolicyName)s ")
|
||||
% {'fastPolicyName': fastPolicyName})
|
||||
LOG.error(exceptionMessage)
|
||||
return failedRet
|
||||
|
||||
self.provision.add_members_to_masking_group(
|
||||
conn, controllerConfigService, storageGroupInstanceName,
|
||||
volumeInstance.path, volumeName)
|
||||
# check to see if the volume is in the storage group
|
||||
assocStorageGroupInstanceName = (
|
||||
self.utils.get_storage_group_from_volume(conn,
|
||||
volumeInstance.path))
|
||||
return assocStorageGroupInstanceName
|
||||
|
||||
def _create_default_storage_group(self, conn, controllerConfigService,
|
||||
fastPolicyName, storageGroupName,
|
||||
volumeInstance):
|
||||
"""Create a first volume for the storage group.
|
||||
|
||||
This is necessary because you cannot remove a volume if it is the
|
||||
last in the group. Create the default storage group for the FAST policy
|
||||
Associate the storage group with the tier policy rule.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:param storageGroupName: the storage group name (String)
|
||||
:param volumeInstance: the volume instance
|
||||
:returns: defaultstorageGroupInstanceName - instance name of the
|
||||
default storage group
|
||||
"""
|
||||
failedRet = None
|
||||
firstVolumeInstance = self._create_volume_for_default_volume_group(
|
||||
conn, controllerConfigService, volumeInstance.path)
|
||||
if firstVolumeInstance is None:
|
||||
exceptionMessage = (_(
|
||||
"Failed to create a first volume for storage"
|
||||
" group : %(storageGroupName)s ")
|
||||
% {'storageGroupName': storageGroupName})
|
||||
LOG.error(exceptionMessage)
|
||||
return failedRet
|
||||
|
||||
defaultStorageGroupInstanceName = (
|
||||
self.provision.create_and_get_storage_group(
|
||||
conn, controllerConfigService, storageGroupName,
|
||||
firstVolumeInstance.path))
|
||||
if defaultStorageGroupInstanceName is None:
|
||||
exceptionMessage = (_(
|
||||
"Failed to create default storage group for "
|
||||
"FAST policy : %(fastPolicyName)s ")
|
||||
% {'fastPolicyName': fastPolicyName})
|
||||
LOG.error(exceptionMessage)
|
||||
return failedRet
|
||||
|
||||
storageSystemInstanceName = (
|
||||
self.utils.find_storage_system(conn, controllerConfigService))
|
||||
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
|
||||
conn, storageSystemInstanceName)
|
||||
|
||||
# get the fast policy instance name
|
||||
tierPolicyRuleInstanceName = self._get_service_level_tier_policy(
|
||||
conn, tierPolicyServiceInstanceName, fastPolicyName)
|
||||
if tierPolicyRuleInstanceName is None:
|
||||
exceptionMessage = (_(
|
||||
"Unable to get policy rule for fast policy: "
|
||||
"%(fastPolicyName)s ")
|
||||
% {'fastPolicyName': fastPolicyName})
|
||||
LOG.error(exceptionMessage)
|
||||
return failedRet
|
||||
|
||||
# now associate it with a FAST policy
|
||||
self.add_storage_group_to_tier_policy_rule(
|
||||
conn, tierPolicyServiceInstanceName,
|
||||
defaultStorageGroupInstanceName, tierPolicyRuleInstanceName,
|
||||
storageGroupName, fastPolicyName)
|
||||
|
||||
return defaultStorageGroupInstanceName
|
||||
|
||||
def _create_volume_for_default_volume_group(
|
||||
self, conn, controllerConfigService, volumeInstanceName):
|
||||
"""Creates a volume for the default storage group for a fast policy.
|
||||
|
||||
Creates a small first volume for the default storage group for a
|
||||
fast policy. This is necessary because you cannot remove
|
||||
the last volume from a storage group and this scenario is likely
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:returns: firstVolumeInstanceName - instance name of the first volume
|
||||
in the storage group
|
||||
"""
|
||||
failedRet = None
|
||||
storageSystemName = self.utils.find_storage_system_name_from_service(
|
||||
controllerConfigService)
|
||||
storageConfigurationInstanceName = (
|
||||
self.utils.find_storage_configuration_service(
|
||||
conn, storageSystemName))
|
||||
|
||||
poolInstanceName = self.utils.get_assoc_pool_from_volume(
|
||||
conn, volumeInstanceName)
|
||||
if poolInstanceName is None:
|
||||
exceptionMessage = (_("Unable to get associated pool of volume"))
|
||||
LOG.error(exceptionMessage)
|
||||
return failedRet
|
||||
|
||||
volumeName = 'vol1'
|
||||
volumeSize = '1'
|
||||
volumeDict, rc = self.provision.create_volume_from_pool(
|
||||
conn, storageConfigurationInstanceName, volumeName,
|
||||
poolInstanceName, volumeSize)
|
||||
firstVolumeInstanceName = self.utils.find_volume_instance(
|
||||
conn, volumeDict, volumeName)
|
||||
return firstVolumeInstanceName
|
||||
|
||||
def add_storage_group_to_tier_policy_rule(
|
||||
self, conn, tierPolicyServiceInstanceName,
|
||||
storageGroupInstanceName, tierPolicyRuleInstanceName,
|
||||
storageGroupName, fastPolicyName):
|
||||
"""Add the storage group to the tier policy rule.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyServiceInstanceName: tier policy service
|
||||
:param storageGroupInstanceName: storage group instance name
|
||||
:param tierPolicyRuleInstanceName: tier policy instance name
|
||||
:param storageGroupName: the storage group name (String)
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
"""
|
||||
# 5 is ("Add InElements to Policy")
|
||||
modificationType = '5'
|
||||
|
||||
rc, job = conn.InvokeMethod(
|
||||
'ModifyStorageTierPolicyRule', tierPolicyServiceInstanceName,
|
||||
PolicyRule=tierPolicyRuleInstanceName,
|
||||
Operation=self.utils.get_num(modificationType, '16'),
|
||||
InElements=[storageGroupInstanceName])
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error associating storage group : %(storageGroupName)s. "
|
||||
"To fast Policy: %(fastPolicyName)s with error "
|
||||
"description: %(errordesc)s")
|
||||
% {'storageGroupName': storageGroupName,
|
||||
'fastPolicyName': fastPolicyName,
|
||||
'errordesc': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
return rc
|
||||
|
||||
def _get_service_level_tier_policy(
|
||||
self, conn, tierPolicyServiceInstanceName, fastPolicyName):
|
||||
"""Returns the existing tier policies for a storage system instance.
|
||||
|
||||
Given the storage system instance name, get the existing tier
|
||||
policies on that array
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyServiceInstanceName: the policy service
|
||||
:param fastPolicyName: the fast policy name e.g BRONZE1
|
||||
:returns: foundTierPolicyRuleInstanceName - the short name,
|
||||
everything after the :
|
||||
"""
|
||||
foundTierPolicyRuleInstanceName = None
|
||||
|
||||
tierPolicyRuleInstanceNames = self._get_existing_tier_policies(
|
||||
conn, tierPolicyServiceInstanceName)
|
||||
|
||||
for tierPolicyRuleInstanceName in tierPolicyRuleInstanceNames:
|
||||
policyRuleName = tierPolicyRuleInstanceName['PolicyRuleName']
|
||||
if fastPolicyName == policyRuleName:
|
||||
foundTierPolicyRuleInstanceName = tierPolicyRuleInstanceName
|
||||
break
|
||||
|
||||
return foundTierPolicyRuleInstanceName
|
||||
|
||||
def _get_existing_tier_policies(self, conn, tierPolicyServiceInstanceName):
|
||||
"""Given the tier policy service, get the existing tier policies.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyServiceInstanceName: the tier policy service
|
||||
instance Name
|
||||
:returns: tierPolicyRuleInstanceNames - the tier policy rule
|
||||
instance names
|
||||
"""
|
||||
tierPolicyRuleInstanceNames = conn.AssociatorNames(
|
||||
tierPolicyServiceInstanceName, ResultClass='Symm_TierPolicyRule')
|
||||
|
||||
return tierPolicyRuleInstanceNames
|
||||
|
||||
def get_associated_tier_policy_from_storage_group(
|
||||
self, conn, storageGroupInstanceName):
|
||||
"""Given the tier policy instance name get the storage groups.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageGroupInstanceName: the storage group instance name
|
||||
:returns: tierPolicyInstanceNames - the list of tier policy
|
||||
instance names
|
||||
"""
|
||||
tierPolicyInstanceName = None
|
||||
|
||||
tierPolicyInstanceNames = conn.AssociatorNames(
|
||||
storageGroupInstanceName,
|
||||
AssocClass='CIM_TierPolicySetAppliesToElement',
|
||||
ResultClass='CIM_TierPolicyRule')
|
||||
|
||||
if (len(tierPolicyInstanceNames) > 0 and
|
||||
len(tierPolicyInstanceNames) < 2):
|
||||
tierPolicyInstanceName = tierPolicyInstanceNames[0]
|
||||
|
||||
return tierPolicyInstanceName
|
||||
|
||||
def get_associated_tier_from_tier_policy(
|
||||
self, conn, tierPolicyRuleInstanceName):
|
||||
"""Given the tierPolicyInstanceName get the associated tiers.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyRuleInstanceName: the tier policy rule instance name
|
||||
:returns: storageTierInstanceNames - a list of storage tier
|
||||
instance names
|
||||
"""
|
||||
storageTierInstanceNames = conn.AssociatorNames(
|
||||
tierPolicyRuleInstanceName,
|
||||
AssocClass='CIM_AssociatedTierPolicy')
|
||||
|
||||
if len(storageTierInstanceNames) == 0:
|
||||
storageTierInstanceNames = None
|
||||
LOG.warn(_("Unable to get storage tiers from tier policy rule "))
|
||||
|
||||
return storageTierInstanceNames
|
||||
|
||||
def get_policy_default_storage_group(
|
||||
self, conn, controllerConfigService, policyName):
|
||||
"""Returns the default storage group for a tier policy.
|
||||
|
||||
Given the tier policy instance name get the associated default
|
||||
storage group.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param controllerConfigService: ControllerConfigurationService
|
||||
instance name
|
||||
:param policyName: string value
|
||||
:returns: storageGroupInstanceName - instance name of the default
|
||||
storage group
|
||||
"""
|
||||
storageMaskingGroupInstanceNames = conn.AssociatorNames(
|
||||
controllerConfigService, ResultClass='CIM_DeviceMaskingGroup')
|
||||
|
||||
for storageMaskingGroupInstanceName in \
|
||||
storageMaskingGroupInstanceNames:
|
||||
storageMaskingGroupInstance = conn.GetInstance(
|
||||
storageMaskingGroupInstanceName)
|
||||
if ('_default_' in storageMaskingGroupInstance['ElementName'] and
|
||||
policyName in storageMaskingGroupInstance['ElementName']):
|
||||
return storageMaskingGroupInstanceName
|
||||
|
||||
return None
|
||||
|
||||
def _get_associated_storage_groups_from_tier_policy(
|
||||
self, conn, tierPolicyInstanceName):
|
||||
"""Given the tier policy instance name get the storage groups.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param tierPolicyInstanceName: tier policy instance name
|
||||
:returns: managedElementInstanceNames - the list of storage
|
||||
instance names
|
||||
"""
|
||||
managedElementInstanceNames = conn.AssociatorNames(
|
||||
tierPolicyInstanceName,
|
||||
AssocClass='CIM_TierPolicySetAppliesToElement',
|
||||
ResultClass='CIM_DeviceMaskingGroup')
|
||||
|
||||
return managedElementInstanceNames
|
||||
|
||||
def get_associated_pools_from_tier(
|
||||
self, conn, storageTierInstanceName):
|
||||
"""Given the storage tier instance name get the storage pools.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageTierInstanceName: the storage tier instance name
|
||||
:returns: storagePoolInstanceNames - a list of storage tier
|
||||
instance names
|
||||
"""
|
||||
storagePoolInstanceNames = conn.AssociatorNames(
|
||||
storageTierInstanceName,
|
||||
AssocClass='CIM_MemberOfCollection',
|
||||
ResultClass='CIM_StoragePool')
|
||||
|
||||
return storagePoolInstanceNames
|
||||
|
||||
def add_storage_group_and_verify_tier_policy_assoc(
|
||||
self, conn, controllerConfigService, storageGroupInstanceName,
|
||||
storageGroupName, fastPolicyName):
|
||||
"""Adds a storage group to a tier policy and verifies success.
|
||||
|
||||
Add a storage group to a tier policy rule and verify that it was
|
||||
successful by getting the association
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param controllerConfigService: the controller config service
|
||||
:param storageGroupInstanceName: the storage group instance name
|
||||
:param storageGroupName: the storage group name (String)
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:returns: assocTierPolicyInstanceName
|
||||
"""
|
||||
failedRet = None
|
||||
assocTierPolicyInstanceName = None
|
||||
storageSystemInstanceName = self.utils.find_storage_system(
|
||||
conn, controllerConfigService)
|
||||
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
|
||||
conn, storageSystemInstanceName)
|
||||
# get the fast policy instance name
|
||||
tierPolicyRuleInstanceName = self._get_service_level_tier_policy(
|
||||
conn, tierPolicyServiceInstanceName, fastPolicyName)
|
||||
if tierPolicyRuleInstanceName is None:
|
||||
errorMessage = (_(
|
||||
"Cannot find the fast policy %(fastPolicyName)s")
|
||||
% {'fastPolicyName': fastPolicyName})
|
||||
|
||||
LOG.error(errorMessage)
|
||||
return failedRet
|
||||
else:
|
||||
LOG.debug(
|
||||
"Adding storage group %(storageGroupInstanceName)s to"
|
||||
" tier policy rule %(tierPolicyRuleInstanceName)s"
|
||||
% {'storageGroupInstanceName': storageGroupInstanceName,
|
||||
'tierPolicyRuleInstanceName': tierPolicyRuleInstanceName})
|
||||
|
||||
# Associate the new storage group with the existing fast policy
|
||||
try:
|
||||
self.add_storage_group_to_tier_policy_rule(
|
||||
conn, tierPolicyServiceInstanceName,
|
||||
storageGroupInstanceName, tierPolicyRuleInstanceName,
|
||||
storageGroupName, fastPolicyName)
|
||||
except Exception as ex:
|
||||
LOG.error(_("Exception: %s") % six.text_type(ex))
|
||||
errorMessage = (_(
|
||||
"Failed to add storage group %(storageGroupInstanceName)s "
|
||||
" to tier policy rule %(tierPolicyRuleInstanceName)s")
|
||||
% {'storageGroupInstanceName': storageGroupInstanceName,
|
||||
'tierPolicyRuleInstanceName':
|
||||
tierPolicyRuleInstanceName})
|
||||
LOG.error(errorMessage)
|
||||
return failedRet
|
||||
|
||||
# check that the storage group has been associated with with the
|
||||
# tier policy rule
|
||||
assocTierPolicyInstanceName = (
|
||||
self.get_associated_tier_policy_from_storage_group(
|
||||
conn, storageGroupInstanceName))
|
||||
|
||||
LOG.debug(
|
||||
"AssocTierPolicyInstanceName is "
|
||||
"%(assocTierPolicyInstanceName)s "
|
||||
% {'assocTierPolicyInstanceName': assocTierPolicyInstanceName})
|
||||
return assocTierPolicyInstanceName
|
||||
|
||||
def get_associated_policy_from_storage_group(
|
||||
self, conn, storageGroupInstanceName):
|
||||
"""Get the tier policy instance name for a storage group instance name.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageGroupInstanceName: storage group instance name
|
||||
:returns: foundTierPolicyInstanceName - instance name of the
|
||||
tier policy object
|
||||
"""
|
||||
foundTierPolicyInstanceName = None
|
||||
|
||||
tierPolicyInstanceNames = conn.AssociatorNames(
|
||||
storageGroupInstanceName,
|
||||
ResultClass='Symm_TierPolicyRule',
|
||||
AssocClass='Symm_TierPolicySetAppliesToElement')
|
||||
|
||||
if len(tierPolicyInstanceNames) > 0:
|
||||
foundTierPolicyInstanceName = tierPolicyInstanceNames[0]
|
||||
|
||||
return foundTierPolicyInstanceName
|
||||
|
||||
def delete_storage_group_from_tier_policy_rule(
|
||||
self, conn, tierPolicyServiceInstanceName,
|
||||
storageGroupInstanceName, tierPolicyRuleInstanceName):
|
||||
"""Disassociate the storage group from its tier policy rule.
|
||||
|
||||
:param conn: connection the ecom server
|
||||
:param tierPolicyServiceInstanceName: instance name of the tier policy
|
||||
service
|
||||
:param storageGroupInstanceName: instance name of the storage group
|
||||
:param tierPolicyRuleInstanceName: instance name of the tier policy
|
||||
associated with the storage group
|
||||
"""
|
||||
modificationType = '6'
|
||||
LOG.debug("Invoking ModifyStorageTierPolicyRule"
|
||||
" %s" % tierPolicyRuleInstanceName)
|
||||
try:
|
||||
rc, job = conn.InvokeMethod(
|
||||
'ModifyStorageTierPolicyRule', tierPolicyServiceInstanceName,
|
||||
PolicyRule=tierPolicyRuleInstanceName,
|
||||
Operation=self.utils.get_num(modificationType, '16'),
|
||||
InElements=[storageGroupInstanceName])
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
LOG.error(_("Error disassociating storage group from "
|
||||
"policy: %s") % errordesc)
|
||||
else:
|
||||
LOG.debug("Disassociated storage group from policy %s")
|
||||
else:
|
||||
LOG.debug("ModifyStorageTierPolicyRule completed")
|
||||
except Exception as e:
|
||||
LOG.info(_("Storage group not associated with the policy %s")
|
||||
% six.text_type(e))
|
||||
|
||||
def get_pool_associated_to_policy(
|
||||
self, conn, fastPolicyName, arraySN,
|
||||
storageConfigService, poolInstanceName):
|
||||
"""Given a FAST policy check that the pool is linked to the policy.
|
||||
|
||||
If it's associated return the pool instance, if not return None.
|
||||
First check if FAST is enabled on the array
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:param arraySN: the array serial number (String)
|
||||
:param storageConfigService: the storage Config Service
|
||||
:param poolInstanceName: the pool instance we want to check for
|
||||
association with the fast storage tier
|
||||
:returns: foundPoolInstanceName
|
||||
"""
|
||||
storageSystemInstanceName = self.utils.find_storage_system(
|
||||
conn, storageConfigService)
|
||||
|
||||
if not self._check_if_fast_supported(conn, storageSystemInstanceName):
|
||||
errorMessage = (_(
|
||||
"FAST is not supported on this array "))
|
||||
LOG.error(errorMessage)
|
||||
exception.VolumeBackendAPIException(data=errorMessage)
|
||||
|
||||
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
|
||||
conn, storageSystemInstanceName)
|
||||
|
||||
tierPolicyRuleInstanceName = self._get_service_level_tier_policy(
|
||||
conn, tierPolicyServiceInstanceName, fastPolicyName)
|
||||
# Get the associated storage tiers from the tier policy rule
|
||||
storageTierInstanceNames = self.get_associated_tier_from_tier_policy(
|
||||
conn, tierPolicyRuleInstanceName)
|
||||
|
||||
# For each gold storage tier get the associated pools
|
||||
foundPoolInstanceName = None
|
||||
for storageTierInstanceName in storageTierInstanceNames:
|
||||
assocStoragePoolInstanceNames = (
|
||||
self.get_associated_pools_from_tier(conn,
|
||||
storageTierInstanceName))
|
||||
for assocStoragePoolInstanceName in assocStoragePoolInstanceNames:
|
||||
if poolInstanceName == assocStoragePoolInstanceName:
|
||||
foundPoolInstanceName = poolInstanceName
|
||||
break
|
||||
if foundPoolInstanceName is not None:
|
||||
break
|
||||
|
||||
return foundPoolInstanceName
|
||||
|
||||
def is_tiering_policy_enabled_on_storage_system(
|
||||
self, conn, storageSystemInstanceName):
|
||||
"""Checks if tiering policy in enabled on a storage system.
|
||||
|
||||
True if FAST policy enabled on the given storage system;
|
||||
False otherwise.
|
||||
|
||||
:param storageSystemInstanceName: a storage system instance name
|
||||
:returns: boolean
|
||||
"""
|
||||
try:
|
||||
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
|
||||
conn, storageSystemInstanceName)
|
||||
isTieringPolicySupported = self.is_tiering_policy_enabled(
|
||||
conn, tierPolicyServiceInstanceName)
|
||||
except Exception as e:
|
||||
LOG.error(_("Exception: %s") % six.text_type(e))
|
||||
return False
|
||||
|
||||
return isTieringPolicySupported
|
||||
|
||||
def get_tier_policy_by_name(
|
||||
self, conn, arrayName, policyName):
|
||||
"""Given the name of the policy, get the TierPolicyRule instance name.
|
||||
|
||||
:param policyName: the name of policy rule, a string value
|
||||
:returns: tierPolicyInstanceName - tier policy instance name
|
||||
"""
|
||||
tierPolicyInstanceNames = conn.EnumerateInstanceNames(
|
||||
'Symm_TierPolicyRule')
|
||||
for policy in tierPolicyInstanceNames:
|
||||
if (policyName == policy['PolicyRuleName'] and
|
||||
arrayName in policy['SystemName']):
|
||||
return policy
|
||||
return None
|
||||
|
||||
def get_capacities_associated_to_policy(self, conn, arrayName, policyName):
|
||||
"""Gets the total and un-used capacities for all pools in a policy.
|
||||
|
||||
Given the name of the policy, get the total capcity and un-used
|
||||
capacity in GB of all the storage pools associated with the policy.
|
||||
|
||||
:param policyName: the name of policy rule, a string value
|
||||
:returns: total_capacity_gb - total capacity in GB of all pools
|
||||
associated with the policy
|
||||
:returns: free_capacity_gb - (total capacity-EMCSubscribedCapacity)
|
||||
in GB of all pools associated with
|
||||
the policy
|
||||
"""
|
||||
policyInstanceName = self.get_tier_policy_by_name(
|
||||
conn, arrayName, policyName)
|
||||
|
||||
total_capacity_gb = 0
|
||||
allocated_capacity_gb = 0
|
||||
|
||||
tierInstanceNames = self.get_associated_tier_from_tier_policy(
|
||||
conn, policyInstanceName)
|
||||
for tierInstanceName in tierInstanceNames:
|
||||
poolInsttanceNames = self.get_associated_pools_from_tier(
|
||||
conn, tierInstanceName)
|
||||
for poolInstanceName in poolInsttanceNames:
|
||||
storagePoolInstance = conn.GetInstance(
|
||||
poolInstanceName, LocalOnly=False)
|
||||
total_capacity_gb += self.utils.convert_bits_to_gbs(
|
||||
storagePoolInstance['TotalManagedSpace'])
|
||||
allocated_capacity_gb += self.utils.convert_bits_to_gbs(
|
||||
storagePoolInstance['EMCSubscribedCapacity'])
|
||||
LOG.debug(
|
||||
"policyName:%(policyName)s, pool: %(poolInstanceName)s, "
|
||||
"allocated_capacity_gb = %(allocated_capacity_gb)lu"
|
||||
% {'policyName': policyName,
|
||||
'poolInstanceName': poolInstanceName,
|
||||
'allocated_capacity_gb': allocated_capacity_gb})
|
||||
|
||||
free_capacity_gb = total_capacity_gb - allocated_capacity_gb
|
||||
return (total_capacity_gb, free_capacity_gb)
|
||||
|
||||
def get_or_create_default_storage_group(
|
||||
self, conn, controllerConfigService, fastPolicyName,
|
||||
volumeInstance):
|
||||
"""Create or get a default storage group for FAST policy.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param fastPolicyName: the fast policy name (String)
|
||||
:param volumeInstance: the volume instance
|
||||
:returns: defaultStorageGroupInstanceName - the default storage group
|
||||
instance name
|
||||
"""
|
||||
defaultSgGroupName = (DEFAULT_SG_PREFIX + fastPolicyName +
|
||||
DEFAULT_SG_POSTFIX)
|
||||
defaultStorageGroupInstanceName = (
|
||||
self.utils.find_storage_masking_group(conn,
|
||||
controllerConfigService,
|
||||
defaultSgGroupName))
|
||||
if defaultStorageGroupInstanceName is None:
|
||||
# create it and associate it with the FAST policy in question
|
||||
defaultStorageGroupInstanceName = (
|
||||
self._create_default_storage_group(conn,
|
||||
controllerConfigService,
|
||||
fastPolicyName,
|
||||
defaultSgGroupName,
|
||||
volumeInstance))
|
||||
|
||||
return defaultStorageGroupInstanceName
|
||||
|
||||
def _get_associated_tier_policy_from_pool(self, conn, poolInstanceName):
|
||||
"""Given the pool instance name get the associated FAST tier policy.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param poolInstanceName: the pool instance name
|
||||
:param fastPolicyName: the FAST Policy name (if it exists)
|
||||
"""
|
||||
fastPolicyName = None
|
||||
|
||||
storageTierInstanceNames = conn.AssociatorNames(
|
||||
poolInstanceName,
|
||||
AssocClass='CIM_MemberOfCollection',
|
||||
ResultClass='CIM_StorageTier')
|
||||
|
||||
if len(storageTierInstanceNames) > 0:
|
||||
tierPolicyInstanceNames = conn.AssociatorNames(
|
||||
storageTierInstanceNames[0],
|
||||
AssocClass='CIM_AssociatedTierPolicy')
|
||||
|
||||
if len(tierPolicyInstanceNames) > 0:
|
||||
tierPolicyInstanceName = tierPolicyInstanceNames[0]
|
||||
fastPolicyName = tierPolicyInstanceName['PolicyRuleName']
|
||||
|
||||
return fastPolicyName
|
|
@ -12,35 +12,34 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
FC Drivers for EMC VNX and VMAX arrays based on SMI-S.
|
||||
|
||||
"""
|
||||
import six
|
||||
|
||||
from cinder import context
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.emc import emc_smis_common
|
||||
from cinder.volume.drivers.emc import emc_vmax_common
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EMCSMISFCDriver(driver.FibreChannelDriver):
|
||||
"""EMC FC Drivers for VMAX and VNX using SMI-S.
|
||||
class EMCVMAXFCDriver(driver.FibreChannelDriver):
|
||||
"""EMC FC Drivers for VMAX using SMI-S.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Multiple pools and thick/thin provisioning,
|
||||
performance enhancement.
|
||||
2.0.0 - Add driver requirement functions
|
||||
"""
|
||||
|
||||
VERSION = "1.1.0"
|
||||
VERSION = "2.0.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCSMISFCDriver, self).__init__(*args, **kwargs)
|
||||
self.common = emc_smis_common.EMCSMISCommon(
|
||||
super(EMCVMAXFCDriver, self).__init__(*args, **kwargs)
|
||||
self.common = emc_vmax_common.EMCVMAXCommon(
|
||||
'FC',
|
||||
configuration=self.configuration)
|
||||
|
||||
|
@ -52,7 +51,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
volpath = self.common.create_volume(volume)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -61,7 +60,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -70,7 +69,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
volpath = self.common.create_cloned_volume(volume, src_vref)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -89,7 +88,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
volpath = self.common.create_snapshot(snapshot, volume)
|
||||
|
||||
model_update = {}
|
||||
snapshot['provider_location'] = str(volpath)
|
||||
snapshot['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = snapshot['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -130,7 +129,6 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
The target_wwn can be a single entry or a list of wwns that
|
||||
correspond to the list of remote wwn(s) that will export the volume.
|
||||
Example return values:
|
||||
|
||||
{
|
||||
'driver_volume_type': 'fibre_channel'
|
||||
'data': {
|
||||
|
@ -150,10 +148,9 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
'target_wwn': ['1234567890123', '0987654321321'],
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
device_info = self.common.initialize_connection(volume,
|
||||
connector)
|
||||
device_info, ipAddress = self.common.initialize_connection(
|
||||
volume, connector)
|
||||
device_number = device_info['hostlunid']
|
||||
storage_system = device_info['storagesystem']
|
||||
target_wwns, init_targ_map = self._build_initiator_target_map(
|
||||
|
@ -165,26 +162,41 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
'target_wwn': target_wwns,
|
||||
'initiator_target_map': init_targ_map}}
|
||||
|
||||
LOG.debug('Return FC data: %(data)s.'
|
||||
LOG.debug("Return FC data: %(data)s."
|
||||
% {'data': data})
|
||||
|
||||
return data
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
"""Disallow connection from connector.
|
||||
|
||||
Return empty data if other volumes are in the same zone.
|
||||
The FibreChannel ZoneManager doesn't remove zones
|
||||
if there isn't an initiator_target_map in the
|
||||
return of terminate_connection.
|
||||
|
||||
:returns: data - the target_wwns and initiator_target_map if the
|
||||
zone is to be removed, otherwise empty
|
||||
"""
|
||||
self.common.terminate_connection(volume, connector)
|
||||
|
||||
loc = volume['provider_location']
|
||||
name = eval(loc)
|
||||
storage_system = name['keybindings']['SystemName']
|
||||
target_wwns, init_targ_map = self._build_initiator_target_map(
|
||||
storage_system, connector)
|
||||
data = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_wwn': target_wwns,
|
||||
'initiator_target_map': init_targ_map}}
|
||||
|
||||
LOG.debug('Return FC data: %(data)s.'
|
||||
numVolumes = self.common.get_num_volumes_mapped(volume, connector)
|
||||
if numVolumes > 0:
|
||||
data = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {}}
|
||||
else:
|
||||
target_wwns, init_targ_map = self._build_initiator_target_map(
|
||||
storage_system, connector)
|
||||
data = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_wwn': target_wwns,
|
||||
'initiator_target_map': init_targ_map}}
|
||||
|
||||
LOG.debug("Return FC data: %(data)s."
|
||||
% {'data': data})
|
||||
|
||||
return data
|
||||
|
@ -220,8 +232,33 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||
"""Retrieve stats info from volume group."""
|
||||
LOG.debug("Updating volume stats")
|
||||
data = self.common.update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or 'EMCSMISFCDriver'
|
||||
data['storage_protocol'] = 'FC'
|
||||
data['driver_version'] = self.VERSION
|
||||
self._stats = data
|
||||
|
||||
def migrate_volume(self, ctxt, volume, host):
|
||||
"""Migrate a volume from one Volume Backend to another.
|
||||
|
||||
:param self: reference to class
|
||||
:param ctxt:
|
||||
:param volume: the volume object including the volume_type_id
|
||||
:param host: the host dict holding the relevant target(destination)
|
||||
information
|
||||
:returns: moved
|
||||
:returns: list
|
||||
"""
|
||||
return self.common.migrate_volume(ctxt, volume, host)
|
||||
|
||||
def retype(self, ctxt, volume, new_type, diff, host):
|
||||
"""Migrate volume to another host using retype.
|
||||
|
||||
:param self: reference to class
|
||||
:param ctxt:
|
||||
:param volume: the volume object including the volume_type_id
|
||||
:param new_type: the new volume type.
|
||||
:param host: the host dict holding the relevant
|
||||
target(destination) information
|
||||
:returns: moved
|
||||
"returns: list
|
||||
"""
|
||||
return self.common.retype(ctxt, volume, new_type, diff, host)
|
|
@ -13,37 +13,38 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
ISCSI Drivers for EMC VNX and VMAX arrays based on SMI-S.
|
||||
ISCSI Drivers for EMC VMAX arrays based on SMI-S.
|
||||
|
||||
"""
|
||||
|
||||
import six
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.emc import emc_smis_common
|
||||
from cinder.volume.drivers.emc import emc_vmax_common
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
||||
"""EMC ISCSI Drivers for VMAX and VNX using SMI-S.
|
||||
class EMCVMAXISCSIDriver(driver.ISCSIDriver):
|
||||
"""EMC ISCSI Drivers for VMAX using SMI-S.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Multiple pools and thick/thin provisioning,
|
||||
performance enhancement.
|
||||
2.0.0 - Add driver requirement functions
|
||||
"""
|
||||
|
||||
VERSION = "1.1.0"
|
||||
VERSION = "2.0.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCSMISISCSIDriver, self).__init__(*args, **kwargs)
|
||||
super(EMCVMAXISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.common =\
|
||||
emc_smis_common.EMCSMISCommon('iSCSI',
|
||||
emc_vmax_common.EMCVMAXCommon('iSCSI',
|
||||
configuration=self.configuration)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
|
@ -54,7 +55,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
volpath = self.common.create_volume(volume)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -63,7 +64,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -72,7 +73,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
volpath = self.common.create_cloned_volume(volume, src_vref)
|
||||
|
||||
model_update = {}
|
||||
volume['provider_location'] = str(volpath)
|
||||
volume['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -91,7 +92,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
volpath = self.common.create_snapshot(snapshot, volume)
|
||||
|
||||
model_update = {}
|
||||
snapshot['provider_location'] = str(volpath)
|
||||
snapshot['provider_location'] = six.text_type(volpath)
|
||||
model_update['provider_location'] = snapshot['provider_location']
|
||||
return model_update
|
||||
|
||||
|
@ -127,7 +128,6 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
The iscsi driver returns a driver_volume_type of 'iscsi'.
|
||||
the format of the driver data is defined in smis_get_iscsi_properties.
|
||||
Example return value::
|
||||
|
||||
{
|
||||
'driver_volume_type': 'iscsi'
|
||||
'data': {
|
||||
|
@ -137,121 +137,104 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
'volume_id': '12345678-1234-4321-1234-123456789012',
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
self.common.initialize_connection(volume, connector)
|
||||
devInfo, ipAddress = self.common.initialize_connection(
|
||||
volume, connector)
|
||||
|
||||
iscsi_properties = self.smis_get_iscsi_properties(volume, connector)
|
||||
iscsi_properties = self.smis_get_iscsi_properties(
|
||||
volume, connector, ipAddress)
|
||||
|
||||
LOG.info(_("Leaving initialize_connection: %s") % (iscsi_properties))
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': iscsi_properties
|
||||
}
|
||||
|
||||
def _do_iscsi_discovery(self, volume):
|
||||
def smis_do_iscsi_discovery(self, volume, ipAddress):
|
||||
|
||||
LOG.warn(_("ISCSI provider_location not stored, using discovery"))
|
||||
|
||||
(out, _err) = self._execute('iscsiadm', '-m', 'discovery',
|
||||
'-t', 'sendtargets', '-p',
|
||||
self.configuration.iscsi_ip_address,
|
||||
ipAddress,
|
||||
run_as_root=True)
|
||||
|
||||
LOG.info(_(
|
||||
"smis_do_iscsi_discovery is: %(out)s")
|
||||
% {'out': out})
|
||||
targets = []
|
||||
for target in out.splitlines():
|
||||
targets.append(target)
|
||||
|
||||
return targets
|
||||
|
||||
def smis_get_iscsi_properties(self, volume, connector):
|
||||
def smis_get_iscsi_properties(self, volume, connector, ipAddress):
|
||||
"""Gets iscsi configuration.
|
||||
|
||||
We ideally get saved information in the volume entity, but fall back
|
||||
to discovery if need be. Discovery may be completely removed in future
|
||||
The properties are:
|
||||
|
||||
:target_discovered: boolean indicating whether discovery was used
|
||||
|
||||
:target_iqn: the IQN of the iSCSI target
|
||||
|
||||
:target_portal: the portal of the iSCSI target
|
||||
|
||||
:target_lun: the lun of the iSCSI target
|
||||
|
||||
:volume_id: the UUID of the volume
|
||||
|
||||
:auth_method:, :auth_username:, :auth_password:
|
||||
|
||||
the authentication details. Right now, either auth_method is not
|
||||
present meaning no authentication, or auth_method == `CHAP`
|
||||
meaning use CHAP with the specified credentials.
|
||||
"""
|
||||
properties = {}
|
||||
|
||||
location = self._do_iscsi_discovery(volume)
|
||||
location = self.smis_do_iscsi_discovery(volume, ipAddress)
|
||||
if not location:
|
||||
raise exception.InvalidVolume(_("Could not find iSCSI export "
|
||||
" for volume %s") %
|
||||
(volume['name']))
|
||||
" for volume %(volumeName)s")
|
||||
% {'volumeName': volume['name']})
|
||||
|
||||
LOG.debug("ISCSI Discovery: Found %s" % (location))
|
||||
properties['target_discovered'] = True
|
||||
|
||||
device_info = self.common.find_device_number(volume, connector)
|
||||
|
||||
if device_info is None or device_info['hostlunid'] is None:
|
||||
exception_message = (_("Cannot find device number for volume %s")
|
||||
% volume['name'])
|
||||
exception_message = (_("Cannot find device number for volume "
|
||||
"%(volumeName)s")
|
||||
% {'volumeName': volume['name']})
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
device_number = device_info['hostlunid']
|
||||
storage_system = device_info['storagesystem']
|
||||
|
||||
# sp is "SP_A" or "SP_B"
|
||||
sp = device_info['owningsp']
|
||||
endpoints = []
|
||||
if sp:
|
||||
# endpoints example:
|
||||
# [iqn.1992-04.com.emc:cx.apm00123907237.a8,
|
||||
# iqn.1992-04.com.emc:cx.apm00123907237.a9]
|
||||
endpoints = self.common._find_iscsi_protocol_endpoints(
|
||||
sp, storage_system)
|
||||
LOG.info(_(
|
||||
"location is: %(location)s") % {'location': location})
|
||||
|
||||
foundEndpoint = False
|
||||
for loc in location:
|
||||
results = loc.split(" ")
|
||||
properties['target_portal'] = results[0].split(",")[0]
|
||||
properties['target_iqn'] = results[1]
|
||||
# owning sp is None for VMAX
|
||||
# for VNX, find the target_iqn that matches the endpoint
|
||||
# target_iqn example: iqn.1992-04.com.emc:cx.apm00123907237.a8
|
||||
# or iqn.1992-04.com.emc:cx.apm00123907237.b8
|
||||
if not sp:
|
||||
break
|
||||
for endpoint in endpoints:
|
||||
if properties['target_iqn'] == endpoint:
|
||||
LOG.debug("Found iSCSI endpoint: %s" % endpoint)
|
||||
foundEndpoint = True
|
||||
break
|
||||
if foundEndpoint:
|
||||
break
|
||||
|
||||
if sp and not foundEndpoint:
|
||||
LOG.warn(_("ISCSI endpoint not found for SP %(sp)s on "
|
||||
"storage system %(storage)s.")
|
||||
% {'sp': sp,
|
||||
'storage': storage_system})
|
||||
|
||||
properties['target_lun'] = device_number
|
||||
|
||||
properties['volume_id'] = volume['id']
|
||||
|
||||
LOG.debug("ISCSI properties: %s" % (properties))
|
||||
LOG.info(_("ISCSI properties: %(properties)s")
|
||||
% {'properties': properties})
|
||||
LOG.info(_("ISCSI volume is: %(volume)s")
|
||||
% {'volume': volume})
|
||||
|
||||
auth = volume['provider_auth']
|
||||
if auth:
|
||||
(auth_method, auth_username, auth_secret) = auth.split()
|
||||
if 'provider_auth' in volume:
|
||||
auth = volume['provider_auth']
|
||||
LOG.info(_("AUTH properties: %(authProps)s")
|
||||
% {'authProps': auth})
|
||||
|
||||
properties['auth_method'] = auth_method
|
||||
properties['auth_username'] = auth_username
|
||||
properties['auth_password'] = auth_secret
|
||||
if auth is not None:
|
||||
(auth_method, auth_username, auth_secret) = auth.split()
|
||||
|
||||
properties['auth_method'] = auth_method
|
||||
properties['auth_username'] = auth_username
|
||||
properties['auth_password'] = auth_secret
|
||||
|
||||
LOG.info(_("AUTH properties: %s") % (properties))
|
||||
|
||||
return properties
|
||||
|
||||
|
@ -277,8 +260,32 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||
"""Retrieve stats info from volume group."""
|
||||
LOG.debug("Updating volume stats")
|
||||
data = self.common.update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or 'EMCSMISISCSIDriver'
|
||||
data['storage_protocol'] = 'iSCSI'
|
||||
data['driver_version'] = self.VERSION
|
||||
self._stats = data
|
||||
|
||||
def migrate_volume(self, ctxt, volume, host):
|
||||
"""Migrate a volume from one Volume Backend to another.
|
||||
:param self: reference to class
|
||||
:param ctxt:
|
||||
:param volume: the volume object including the volume_type_id
|
||||
:param host: the host dict holding the relevant target(destination)
|
||||
information
|
||||
:returns: moved
|
||||
:returns: list
|
||||
"""
|
||||
return self.common.migrate_volume(ctxt, volume, host)
|
||||
|
||||
def retype(self, ctxt, volume, new_type, diff, host):
|
||||
"""Migrate volume to another host using retype.
|
||||
|
||||
:param self: reference to class
|
||||
:param ctxt:
|
||||
:param volume: the volume object including the volume_type_id
|
||||
:param new_type: the new volume type.
|
||||
:param host: the host dict holding the relevant target(destination)
|
||||
information
|
||||
:returns: moved
|
||||
{}
|
||||
"""
|
||||
return self.common.retype(ctxt, volume, new_type, diff, host)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,649 @@
|
|||
# Copyright (c) 2012 - 2014 EMC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume.drivers.emc import emc_vmax_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
STORAGEGROUPTYPE = 4
|
||||
POSTGROUPTYPE = 3
|
||||
|
||||
EMC_ROOT = 'root/emc'
|
||||
THINPROVISIONINGCOMPOSITE = 32768
|
||||
THINPROVISIONING = 5
|
||||
|
||||
|
||||
class EMCVMAXProvision(object):
|
||||
"""Provisioning Class for SMI-S based EMC volume drivers.
|
||||
|
||||
This Provisioning class is for EMC volume drivers based on SMI-S.
|
||||
It supports VMAX arrays.
|
||||
"""
|
||||
def __init__(self, prtcl):
|
||||
self.protocol = prtcl
|
||||
self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl)
|
||||
|
||||
def delete_volume_from_pool(
|
||||
self, conn, storageConfigservice, volumeInstanceName, volumeName):
|
||||
"""Given the volume instance remove it from the pool.
|
||||
|
||||
:param conn: connection the the ecom server
|
||||
:param storageConfigservice: volume created from job
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param volumeName: the volume name (String)
|
||||
:param rc: return code
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'EMCReturnToStoragePool', storageConfigservice,
|
||||
TheElements=[volumeInstanceName])
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Delete Volume: %(volumeName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'volumeName': volumeName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
return rc
|
||||
|
||||
def create_volume_from_pool(
|
||||
self, conn, storageConfigService, volumeName,
|
||||
poolInstanceName, volumeSize):
|
||||
"""Create the volume in the specified pool.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageConfigService: the storage configuration service
|
||||
:param volumeName: the volume name (String)
|
||||
:param poolInstanceName: the pool instance name to create
|
||||
the dummy volume in
|
||||
:param volumeSize: volume size (String)
|
||||
:returns: volumeDict - the volume dict
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateOrModifyElementFromStoragePool',
|
||||
storageConfigService, ElementName=volumeName,
|
||||
InPool=poolInstanceName,
|
||||
ElementType=self.utils.get_num(THINPROVISIONING, '16'),
|
||||
Size=self.utils.get_num(volumeSize, '64'),
|
||||
EMCBindElements=False)
|
||||
|
||||
LOG.debug("Create Volume: %(volumename)s Return code: %(rc)lu"
|
||||
% {'volumename': volumeName,
|
||||
'rc': rc})
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Create Volume: %(volumeName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'volumeName': volumeName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
# Find the newly created volume
|
||||
volumeDict = self.get_volume_dict_from_job(conn, job['Job'])
|
||||
|
||||
return volumeDict, rc
|
||||
|
||||
def create_and_get_storage_group(self, conn, controllerConfigService,
|
||||
storageGroupName, volumeInstanceName):
|
||||
"""Create a storage group and return it.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param storageGroupName: the storage group name (String
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:returns: foundStorageGroupInstanceName - instance name of the
|
||||
default storage group
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateGroup', controllerConfigService, GroupName=storageGroupName,
|
||||
Type=self.utils.get_num(STORAGEGROUPTYPE, '16'),
|
||||
Members=[volumeInstanceName])
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Create Group: %(groupName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'groupName': storageGroupName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
foundStorageGroupInstanceName = self._find_new_storage_group(
|
||||
conn, job, storageGroupName)
|
||||
|
||||
return foundStorageGroupInstanceName
|
||||
|
||||
def create_storage_group_no_members(
|
||||
self, conn, controllerConfigService, groupName):
|
||||
"""Create a new storage group that has no members.
|
||||
|
||||
:param conn: connection the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param groupName: the proposed group name
|
||||
:returns: foundStorageGroupInstanceName - the instance Name of
|
||||
the storage group
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateGroup', controllerConfigService, GroupName=groupName,
|
||||
Type=self.utils.get_num(STORAGEGROUPTYPE, '16'),
|
||||
DeleteWhenBecomesUnassociated=False)
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Create Group: %(groupName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'groupName': groupName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
foundStorageGroupInstanceName = self._find_new_storage_group(
|
||||
conn, job, groupName)
|
||||
|
||||
return foundStorageGroupInstanceName
|
||||
|
||||
def _find_new_storage_group(
|
||||
self, conn, maskingGroupDict, storageGroupName):
|
||||
"""After creating an new storage group find it and return it.
|
||||
|
||||
:param conn: connection the ecom server
|
||||
:param maskingGroupDict: the maskingGroupDict dict
|
||||
:param storageGroupName: storage group name (String)
|
||||
:returns: maskingGroupDict['MaskingGroup']
|
||||
"""
|
||||
foundStorageGroupInstanceName = None
|
||||
if 'MaskingGroup' in maskingGroupDict:
|
||||
foundStorageGroupInstanceName = maskingGroupDict['MaskingGroup']
|
||||
|
||||
return foundStorageGroupInstanceName
|
||||
|
||||
def get_volume_dict_from_job(self, conn, jobInstance):
|
||||
"""Given the jobInstance determine the volume Instance.
|
||||
|
||||
:param conn: the ecom connection
|
||||
:param jobInstance: the instance of a job
|
||||
:returns: volumeDict - an instance of a volume
|
||||
"""
|
||||
associators = conn.Associators(
|
||||
jobInstance,
|
||||
ResultClass='EMC_StorageVolume')
|
||||
volpath = associators[0].path
|
||||
volumeDict = {}
|
||||
volumeDict['classname'] = volpath.classname
|
||||
keys = {}
|
||||
keys['CreationClassName'] = volpath['CreationClassName']
|
||||
keys['SystemName'] = volpath['SystemName']
|
||||
keys['DeviceID'] = volpath['DeviceID']
|
||||
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
||||
volumeDict['keybindings'] = keys
|
||||
|
||||
return volumeDict
|
||||
|
||||
def remove_device_from_storage_group(
|
||||
self, conn, controllerConfigService, storageGroupInstanceName,
|
||||
volumeInstanceName, volumeName):
|
||||
"""Remove a volume from a storage group.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param storageGroupInstanceName: the instance name of the storage group
|
||||
:param volumeInstanceName: the instance name of the volume
|
||||
:param volumeName: the volume name (String)
|
||||
:returns: rc - the return code of the job
|
||||
"""
|
||||
rc, jobDict = conn.InvokeMethod('RemoveMembers',
|
||||
controllerConfigService,
|
||||
MaskingGroup=storageGroupInstanceName,
|
||||
Members=[volumeInstanceName])
|
||||
if rc != 0L:
|
||||
rc, errorDesc = self.utils.wait_for_job_complete(conn, jobDict)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error removing volume %(vol)s. %(error)s")
|
||||
% {'vol': volumeName, 'error': errorDesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
return rc
|
||||
|
||||
def add_members_to_masking_group(
|
||||
self, conn, controllerConfigService, storageGroupInstanceName,
|
||||
volumeInstanceName, volumeName):
|
||||
"""Add a member to a masking group group.
|
||||
:param conn: the connection to the ecom server
|
||||
:param controllerConfigService: the controller configuration service
|
||||
:param storageGroupInstanceName: the instance name of the storage group
|
||||
:param volumeInstanceName: the instance name of the volume
|
||||
:param volumeName: the volume name (String)
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'AddMembers', controllerConfigService,
|
||||
MaskingGroup=storageGroupInstanceName,
|
||||
Members=[volumeInstanceName])
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error mapping volume %(vol)s. %(error)s")
|
||||
% {'vol': volumeName, 'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
def unbind_volume_from_storage_pool(
|
||||
self, conn, storageConfigService, poolInstanceName,
|
||||
volumeInstanceName, volumeName):
|
||||
"""Unbind a volume from a pool and return the unbound volume.
|
||||
|
||||
:param conn: the connection information to the ecom server
|
||||
:param storageConfigService: the storage configuration service
|
||||
instance name
|
||||
:param poolInstanceName: the pool instance name
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param volumeName: the volume name
|
||||
:returns: unboundVolumeInstance - the unbound volume instance
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'EMCUnBindElement',
|
||||
storageConfigService,
|
||||
InPool=poolInstanceName,
|
||||
TheElement=volumeInstanceName)
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error unbinding volume %(vol)s from pool. %(error)s")
|
||||
% {'vol': volumeName, 'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
return rc, job
|
||||
|
||||
def modify_composite_volume(
|
||||
self, conn, elementCompositionService, theVolumeInstanceName,
|
||||
inVolumeInstanceName):
|
||||
|
||||
"""Given a composite volume add a storage volume to it.
|
||||
|
||||
:param conn: the connection to the ecom
|
||||
:param elementCompositionService: the element composition service
|
||||
:param theVolumeInstanceName: the existing composite volume
|
||||
:param inVolumeInstanceName: the volume you wish to add to the
|
||||
composite volume
|
||||
:returns: rc - return code
|
||||
:returns: job - job
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateOrModifyCompositeElement',
|
||||
elementCompositionService,
|
||||
TheElement=theVolumeInstanceName,
|
||||
InElements=[inVolumeInstanceName])
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error adding volume to composite volume. "
|
||||
"Error is: %(error)s")
|
||||
% {'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc, job
|
||||
|
||||
def create_composite_volume(
|
||||
self, conn, elementCompositionService, volumeSize, volumeName,
|
||||
poolInstanceName, compositeType, numMembers):
|
||||
"""Create a new volume using the auto meta feature.
|
||||
|
||||
:param conn: the connection the the ecom server
|
||||
:param elementCompositionService: the element composition service
|
||||
:param volumeSize: the size of the volume
|
||||
:param volumeName: user friendly name
|
||||
:param poolInstanceName: the pool to bind the composite volume to
|
||||
:param compositeType: the proposed composite type of the volume
|
||||
e.g striped/concatenated
|
||||
:param numMembers: the number of meta members to make up the composite.
|
||||
If it is 1 then a non composite is created
|
||||
:returns: rc
|
||||
:returns: errordesc
|
||||
"""
|
||||
newMembers = 2
|
||||
|
||||
LOG.debug(
|
||||
"Parameters for CreateOrModifyCompositeElement: "
|
||||
"elementCompositionService: %(elementCompositionService)s "
|
||||
"provisioning: %(provisioning)lu "
|
||||
"volumeSize: %(volumeSize)s "
|
||||
"newMembers: %(newMembers)lu "
|
||||
"poolInstanceName: %(poolInstanceName)s "
|
||||
"compositeType: %(compositeType)lu "
|
||||
"numMembers: %(numMembers)s "
|
||||
% {'elementCompositionService': elementCompositionService,
|
||||
'provisioning': THINPROVISIONINGCOMPOSITE,
|
||||
'volumeSize': volumeSize,
|
||||
'newMembers': newMembers,
|
||||
'poolInstanceName': poolInstanceName,
|
||||
'compositeType': compositeType,
|
||||
'numMembers': numMembers})
|
||||
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateOrModifyCompositeElement', elementCompositionService,
|
||||
ElementType=self.utils.get_num(THINPROVISIONINGCOMPOSITE, '16'),
|
||||
Size=self.utils.get_num(volumeSize, '64'),
|
||||
ElementSource=self.utils.get_num(newMembers, '16'),
|
||||
EMCInPools=[poolInstanceName],
|
||||
CompositeType=self.utils.get_num(compositeType, '16'),
|
||||
EMCNumberOfMembers=self.utils.get_num(numMembers, '32'))
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Create Volume: %(volumename)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'volumename': volumeName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
# Find the newly created volume
|
||||
volumeDict = self.get_volume_dict_from_job(conn, job['Job'])
|
||||
|
||||
return volumeDict, rc
|
||||
|
||||
def create_new_composite_volume(
|
||||
self, conn, elementCompositionService, compositeHeadInstanceName,
|
||||
compositeMemberInstanceName, compositeType):
|
||||
"""Creates a new composite volume.
|
||||
|
||||
Given a bound composite head and an unbound composite member
|
||||
create a new composite volume.
|
||||
|
||||
:param conn: the connection the the ecom server
|
||||
:param elementCompositionService: the element composition service
|
||||
:param compositeHeadInstanceName: the composite head. This can be bound
|
||||
:param compositeMemberInstanceName: the composite member.
|
||||
This must be unbound
|
||||
:param compositeType: the composite type e.g striped or concatenated
|
||||
:returns: rc - return code
|
||||
:returns: errordesc - descriptions of the error
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateOrModifyCompositeElement', elementCompositionService,
|
||||
ElementType=self.utils.get_num('2', '16'),
|
||||
InElements=(
|
||||
[compositeHeadInstanceName, compositeMemberInstanceName]),
|
||||
CompositeType=self.utils.get_num(compositeType, '16'))
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Creating new composite Volume Return code: %(rc)lu."
|
||||
"Error: %(error)s")
|
||||
% {'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc, job
|
||||
|
||||
def _migrate_volume(
|
||||
self, conn, storageRelocationServiceInstanceName,
|
||||
volumeInstanceName, targetPoolInstanceName):
|
||||
"""Migrate a volume to another pool.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param storageRelocationServiceInstanceName: the storage relocation
|
||||
service
|
||||
:param volumeInstanceName: the volume to be migrated
|
||||
:param targetPoolInstanceName: the target pool to migrate the volume to
|
||||
:returns: rc - return code
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'RelocateStorageVolumesToStoragePool',
|
||||
storageRelocationServiceInstanceName,
|
||||
TheElements=[volumeInstanceName],
|
||||
TargetPool=targetPoolInstanceName)
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Migrating volume from one pool to another. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc
|
||||
|
||||
def migrate_volume_to_storage_pool(
|
||||
self, conn, storageRelocationServiceInstanceName,
|
||||
volumeInstanceName, targetPoolInstanceName):
|
||||
"""Given the storage system name, get the storage relocation service.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param storageRelocationServiceInstanceName: the storage relocation
|
||||
service
|
||||
:param volumeInstanceName: the volume to be migrated
|
||||
:param targetPoolInstanceName: the target pool to migrate the
|
||||
volume to.
|
||||
:returns: rc
|
||||
"""
|
||||
LOG.debug(
|
||||
"Volume instance name is %(volumeInstanceName)s. "
|
||||
"Pool instance name is : %(targetPoolInstanceName)s. "
|
||||
% {'volumeInstanceName': volumeInstanceName,
|
||||
'targetPoolInstanceName': targetPoolInstanceName})
|
||||
rc = -1
|
||||
try:
|
||||
rc = self._migrate_volume(
|
||||
conn, storageRelocationServiceInstanceName,
|
||||
volumeInstanceName, targetPoolInstanceName)
|
||||
except Exception as ex:
|
||||
if 'source of a migration session' in six.text_type(ex):
|
||||
try:
|
||||
rc = self._terminate_migrate_session(
|
||||
conn, volumeInstanceName)
|
||||
except Exception as ex:
|
||||
LOG.error(_("Exception: %s") % six.text_type(ex))
|
||||
exceptionMessage = (_(
|
||||
"Failed to terminate migrate session"))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
try:
|
||||
rc = self._migrate_volume(
|
||||
conn, storageRelocationServiceInstanceName,
|
||||
volumeInstanceName, targetPoolInstanceName)
|
||||
except Exception as ex:
|
||||
LOG.error(_("Exception: %s") % six.text_type(ex))
|
||||
exceptionMessage = (_(
|
||||
"Failed to migrate volume for the second time"))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
else:
|
||||
LOG.error(_("Exception: %s") % six.text_type(ex))
|
||||
exceptionMessage = (_(
|
||||
"Failed to migrate volume for the first time"))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
return rc
|
||||
|
||||
def _terminate_migrate_session(self, conn, volumeInstanceName):
|
||||
"""Given the volume instance terminate a migrate session.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param volumeInstanceName: the volume to be migrated
|
||||
:returns: rc
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'RequestStateChange', volumeInstanceName,
|
||||
RequestedState=self.utils.get_num(32769, '16'))
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Terminating migrate session. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc
|
||||
|
||||
def create_element_replica(
|
||||
self, conn, repServiceInstanceName, cloneName,
|
||||
sourceName, sourceInstance):
|
||||
"""Make SMI-S call to create replica for source element.
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param repServiceInstanceName: instance name of the replication service
|
||||
:param cloneName: replica name
|
||||
:param sourceName: source volume name
|
||||
:param sourceInstance: source volume instance
|
||||
:returns: rc - return code
|
||||
:returns: job - job object of the replica creation operation
|
||||
"""
|
||||
rc, job = conn.InvokeMethod(
|
||||
'CreateElementReplica', repServiceInstanceName,
|
||||
ElementName=cloneName,
|
||||
SyncType=self.utils.get_num(8, '16'),
|
||||
SourceElement=sourceInstance.path)
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error Create Cloned Volume: "
|
||||
"Volume: %(cloneName)s Source Volume:"
|
||||
"%(sourceName)s. Return code: %(rc)lu. "
|
||||
"Error: %(error)s")
|
||||
% {'cloneName': cloneName,
|
||||
'sourceName': sourceName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc, job
|
||||
|
||||
def delete_clone_relationship(
|
||||
self, conn, repServiceInstanceName, syncInstanceName,
|
||||
cloneName, sourceName):
|
||||
"""Deletes the relationship between the clone and source volume.
|
||||
|
||||
Makes an SMI-S call to break clone relationship between the clone
|
||||
volume and the source
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param repServiceInstanceName: instance name of the replication service
|
||||
:param syncInstanceName: instance name of the
|
||||
SE_StorageSynchronized_SV_SV object
|
||||
:param cloneName: replica name
|
||||
:param sourceName: source volume name
|
||||
:param sourceInstance: source volume instance
|
||||
:returns: rc - return code
|
||||
:returns: job - job object of the replica creation operation
|
||||
"""
|
||||
|
||||
'''
|
||||
8/Detach - Delete the synchronization between two storage objects.
|
||||
Treat the objects as independent after the synchronization is deleted.
|
||||
'''
|
||||
rc, job = conn.InvokeMethod(
|
||||
'ModifyReplicaSynchronization', repServiceInstanceName,
|
||||
Operation=self.utils.get_num(8, '16'),
|
||||
Synchronization=syncInstanceName)
|
||||
|
||||
LOG.debug("Break clone relationship: Volume: %(cloneName)s "
|
||||
"Source Volume: %(sourceName)s Return code: %(rc)lu"
|
||||
% {'cloneName': cloneName,
|
||||
'sourceName': sourceName,
|
||||
'rc': rc})
|
||||
|
||||
if rc != 0L:
|
||||
rc, errordesc = self.utils.wait_for_job_complete(conn, job)
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_(
|
||||
"Error break clone relationship: "
|
||||
"Clone Volume: %(cloneName)s "
|
||||
"Source Volume: %(sourceName)s. "
|
||||
"Return code: %(rc)lu. Error: %(error)s")
|
||||
% {'cloneName': cloneName,
|
||||
'sourceName': sourceName,
|
||||
'rc': rc,
|
||||
'error': errordesc})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
return rc, job
|
||||
|
||||
def get_target_endpoints(self, conn, storageHardwareService, hardwareId):
|
||||
"""Given the hardwareId get the
|
||||
|
||||
:param conn: the connection to the ecom server
|
||||
:param storageHardwareService: the storage HardwareId Service
|
||||
:param hardwareId: the hardware Id
|
||||
:returns: rc
|
||||
:returns: targetendpoints
|
||||
"""
|
||||
rc, targetEndpoints = conn.InvokeMethod(
|
||||
'EMCGetTargetEndpoints', storageHardwareService,
|
||||
HardwareId=hardwareId)
|
||||
|
||||
if rc != 0L:
|
||||
exceptionMessage = (_("Error finding Target WWNs."))
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
return rc, targetEndpoints
|
File diff suppressed because it is too large
Load Diff
|
@ -1093,10 +1093,10 @@
|
|||
|
||||
|
||||
#
|
||||
# Options defined in cinder.volume.drivers.emc.emc_smis_common
|
||||
# Options defined in cinder.volume.drivers.emc.emc_vmax_common
|
||||
#
|
||||
|
||||
# The configuration file for the Cinder EMC driver (string
|
||||
# use this file for cinder emc plugin config data (string
|
||||
# value)
|
||||
#cinder_emc_config_file=/etc/cinder/cinder_emc_config.xml
|
||||
|
||||
|
|
Loading…
Reference in New Issue