EMC VMAX Driver Juno Update
This driver is an enhancement from the EMC SMI-S driver. In Juno, VNX support will be removed from this driver. Moving forward, this driver will support VMAX only. The following features are added for VMAX: * Extend volume * Create volume from snapshot * Dynamically creating masking views, storage groups, and initiator groups * Striped volumes * FAST policies Tempest test results from CI system: https://bugs.launchpad.net/cinder/+bug/1337840 Change-Id: I01aaf1041d32351a8dc12c509f387e2d120074a3 Implements: blueprint emc-vmax-driver-juno-update
This commit is contained in:
parent
20f5102a67
commit
d4d7b53654
File diff suppressed because it is too large
Load Diff
2783
cinder/tests/test_emc_vmax.py
Normal file
2783
cinder/tests/test_emc_vmax.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2196
cinder/volume/drivers/emc/emc_vmax_common.py
Normal file
2196
cinder/volume/drivers/emc/emc_vmax_common.py
Normal file
File diff suppressed because it is too large
Load Diff
767
cinder/volume/drivers/emc/emc_vmax_fast.py
Normal file
767
cinder/volume/drivers/emc/emc_vmax_fast.py
Normal file
@ -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
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
"""
|
import six
|
||||||
FC Drivers for EMC VNX and VMAX arrays based on SMI-S.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder.openstack.common import log as logging
|
from cinder.openstack.common import log as logging
|
||||||
from cinder.volume import driver
|
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
|
from cinder.zonemanager import utils as fczm_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EMCSMISFCDriver(driver.FibreChannelDriver):
|
class EMCVMAXFCDriver(driver.FibreChannelDriver):
|
||||||
"""EMC FC Drivers for VMAX and VNX using SMI-S.
|
"""EMC FC Drivers for VMAX using SMI-S.
|
||||||
|
|
||||||
Version history:
|
Version history:
|
||||||
1.0.0 - Initial driver
|
1.0.0 - Initial driver
|
||||||
1.1.0 - Multiple pools and thick/thin provisioning,
|
1.1.0 - Multiple pools and thick/thin provisioning,
|
||||||
performance enhancement.
|
performance enhancement.
|
||||||
|
2.0.0 - Add driver requirement functions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "1.1.0"
|
VERSION = "2.0.0"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
super(EMCSMISFCDriver, self).__init__(*args, **kwargs)
|
super(EMCVMAXFCDriver, self).__init__(*args, **kwargs)
|
||||||
self.common = emc_smis_common.EMCSMISCommon(
|
self.common = emc_vmax_common.EMCVMAXCommon(
|
||||||
'FC',
|
'FC',
|
||||||
configuration=self.configuration)
|
configuration=self.configuration)
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
volpath = self.common.create_volume(volume)
|
volpath = self.common.create_volume(volume)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -61,7 +60,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
volpath = self.common.create_cloned_volume(volume, src_vref)
|
volpath = self.common.create_cloned_volume(volume, src_vref)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
volpath = self.common.create_snapshot(snapshot, volume)
|
volpath = self.common.create_snapshot(snapshot, volume)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
snapshot['provider_location'] = str(volpath)
|
snapshot['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = snapshot['provider_location']
|
model_update['provider_location'] = snapshot['provider_location']
|
||||||
return model_update
|
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
|
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.
|
correspond to the list of remote wwn(s) that will export the volume.
|
||||||
Example return values:
|
Example return values:
|
||||||
|
|
||||||
{
|
{
|
||||||
'driver_volume_type': 'fibre_channel'
|
'driver_volume_type': 'fibre_channel'
|
||||||
'data': {
|
'data': {
|
||||||
@ -150,10 +148,9 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
'target_wwn': ['1234567890123', '0987654321321'],
|
'target_wwn': ['1234567890123', '0987654321321'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
device_info = self.common.initialize_connection(volume,
|
device_info, ipAddress = self.common.initialize_connection(
|
||||||
connector)
|
volume, connector)
|
||||||
device_number = device_info['hostlunid']
|
device_number = device_info['hostlunid']
|
||||||
storage_system = device_info['storagesystem']
|
storage_system = device_info['storagesystem']
|
||||||
target_wwns, init_targ_map = self._build_initiator_target_map(
|
target_wwns, init_targ_map = self._build_initiator_target_map(
|
||||||
@ -165,26 +162,41 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
'target_wwn': target_wwns,
|
'target_wwn': target_wwns,
|
||||||
'initiator_target_map': init_targ_map}}
|
'initiator_target_map': init_targ_map}}
|
||||||
|
|
||||||
LOG.debug('Return FC data: %(data)s.'
|
LOG.debug("Return FC data: %(data)s."
|
||||||
% {'data': data})
|
% {'data': data})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@fczm_utils.RemoveFCZone
|
@fczm_utils.RemoveFCZone
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
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)
|
self.common.terminate_connection(volume, connector)
|
||||||
|
|
||||||
loc = volume['provider_location']
|
loc = volume['provider_location']
|
||||||
name = eval(loc)
|
name = eval(loc)
|
||||||
storage_system = name['keybindings']['SystemName']
|
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})
|
% {'data': data})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@ -220,8 +232,33 @@ class EMCSMISFCDriver(driver.FibreChannelDriver):
|
|||||||
"""Retrieve stats info from volume group."""
|
"""Retrieve stats info from volume group."""
|
||||||
LOG.debug("Updating volume stats")
|
LOG.debug("Updating volume stats")
|
||||||
data = self.common.update_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['storage_protocol'] = 'FC'
|
||||||
data['driver_version'] = self.VERSION
|
data['driver_version'] = self.VERSION
|
||||||
self._stats = data
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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 context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.openstack.common import log as logging
|
from cinder.openstack.common import log as logging
|
||||||
from cinder.volume import driver
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
class EMCVMAXISCSIDriver(driver.ISCSIDriver):
|
||||||
"""EMC ISCSI Drivers for VMAX and VNX using SMI-S.
|
"""EMC ISCSI Drivers for VMAX using SMI-S.
|
||||||
|
|
||||||
Version history:
|
Version history:
|
||||||
1.0.0 - Initial driver
|
1.0.0 - Initial driver
|
||||||
1.1.0 - Multiple pools and thick/thin provisioning,
|
1.1.0 - Multiple pools and thick/thin provisioning,
|
||||||
performance enhancement.
|
performance enhancement.
|
||||||
|
2.0.0 - Add driver requirement functions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "1.1.0"
|
VERSION = "2.0.0"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
super(EMCSMISISCSIDriver, self).__init__(*args, **kwargs)
|
super(EMCVMAXISCSIDriver, self).__init__(*args, **kwargs)
|
||||||
self.common =\
|
self.common =\
|
||||||
emc_smis_common.EMCSMISCommon('iSCSI',
|
emc_vmax_common.EMCVMAXCommon('iSCSI',
|
||||||
configuration=self.configuration)
|
configuration=self.configuration)
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
def check_for_setup_error(self):
|
||||||
@ -54,7 +55,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
volpath = self.common.create_volume(volume)
|
volpath = self.common.create_volume(volume)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
volpath = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
volpath = self.common.create_cloned_volume(volume, src_vref)
|
volpath = self.common.create_cloned_volume(volume, src_vref)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
volume['provider_location'] = str(volpath)
|
volume['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = volume['provider_location']
|
model_update['provider_location'] = volume['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
volpath = self.common.create_snapshot(snapshot, volume)
|
volpath = self.common.create_snapshot(snapshot, volume)
|
||||||
|
|
||||||
model_update = {}
|
model_update = {}
|
||||||
snapshot['provider_location'] = str(volpath)
|
snapshot['provider_location'] = six.text_type(volpath)
|
||||||
model_update['provider_location'] = snapshot['provider_location']
|
model_update['provider_location'] = snapshot['provider_location']
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -127,7 +128,6 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
The iscsi driver returns a driver_volume_type of 'iscsi'.
|
The iscsi driver returns a driver_volume_type of 'iscsi'.
|
||||||
the format of the driver data is defined in smis_get_iscsi_properties.
|
the format of the driver data is defined in smis_get_iscsi_properties.
|
||||||
Example return value::
|
Example return value::
|
||||||
|
|
||||||
{
|
{
|
||||||
'driver_volume_type': 'iscsi'
|
'driver_volume_type': 'iscsi'
|
||||||
'data': {
|
'data': {
|
||||||
@ -137,121 +137,104 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
'volume_id': '12345678-1234-4321-1234-123456789012',
|
'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 {
|
return {
|
||||||
'driver_volume_type': 'iscsi',
|
'driver_volume_type': 'iscsi',
|
||||||
'data': iscsi_properties
|
'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"))
|
LOG.warn(_("ISCSI provider_location not stored, using discovery"))
|
||||||
|
|
||||||
(out, _err) = self._execute('iscsiadm', '-m', 'discovery',
|
(out, _err) = self._execute('iscsiadm', '-m', 'discovery',
|
||||||
'-t', 'sendtargets', '-p',
|
'-t', 'sendtargets', '-p',
|
||||||
self.configuration.iscsi_ip_address,
|
ipAddress,
|
||||||
run_as_root=True)
|
run_as_root=True)
|
||||||
|
|
||||||
|
LOG.info(_(
|
||||||
|
"smis_do_iscsi_discovery is: %(out)s")
|
||||||
|
% {'out': out})
|
||||||
targets = []
|
targets = []
|
||||||
for target in out.splitlines():
|
for target in out.splitlines():
|
||||||
targets.append(target)
|
targets.append(target)
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
def smis_get_iscsi_properties(self, volume, connector):
|
def smis_get_iscsi_properties(self, volume, connector, ipAddress):
|
||||||
"""Gets iscsi configuration.
|
"""Gets iscsi configuration.
|
||||||
|
|
||||||
We ideally get saved information in the volume entity, but fall back
|
We ideally get saved information in the volume entity, but fall back
|
||||||
to discovery if need be. Discovery may be completely removed in future
|
to discovery if need be. Discovery may be completely removed in future
|
||||||
The properties are:
|
The properties are:
|
||||||
|
|
||||||
:target_discovered: boolean indicating whether discovery was used
|
:target_discovered: boolean indicating whether discovery was used
|
||||||
|
|
||||||
:target_iqn: the IQN of the iSCSI target
|
:target_iqn: the IQN of the iSCSI target
|
||||||
|
|
||||||
:target_portal: the portal of the iSCSI target
|
:target_portal: the portal of the iSCSI target
|
||||||
|
|
||||||
:target_lun: the lun of the iSCSI target
|
:target_lun: the lun of the iSCSI target
|
||||||
|
|
||||||
:volume_id: the UUID of the volume
|
:volume_id: the UUID of the volume
|
||||||
|
|
||||||
:auth_method:, :auth_username:, :auth_password:
|
:auth_method:, :auth_username:, :auth_password:
|
||||||
|
|
||||||
the authentication details. Right now, either auth_method is not
|
the authentication details. Right now, either auth_method is not
|
||||||
present meaning no authentication, or auth_method == `CHAP`
|
present meaning no authentication, or auth_method == `CHAP`
|
||||||
meaning use CHAP with the specified credentials.
|
meaning use CHAP with the specified credentials.
|
||||||
"""
|
"""
|
||||||
properties = {}
|
properties = {}
|
||||||
|
|
||||||
location = self._do_iscsi_discovery(volume)
|
location = self.smis_do_iscsi_discovery(volume, ipAddress)
|
||||||
if not location:
|
if not location:
|
||||||
raise exception.InvalidVolume(_("Could not find iSCSI export "
|
raise exception.InvalidVolume(_("Could not find iSCSI export "
|
||||||
" for volume %s") %
|
" for volume %(volumeName)s")
|
||||||
(volume['name']))
|
% {'volumeName': volume['name']})
|
||||||
|
|
||||||
LOG.debug("ISCSI Discovery: Found %s" % (location))
|
LOG.debug("ISCSI Discovery: Found %s" % (location))
|
||||||
properties['target_discovered'] = True
|
properties['target_discovered'] = True
|
||||||
|
|
||||||
device_info = self.common.find_device_number(volume, connector)
|
device_info = self.common.find_device_number(volume, connector)
|
||||||
|
|
||||||
if device_info is None or device_info['hostlunid'] is None:
|
if device_info is None or device_info['hostlunid'] is None:
|
||||||
exception_message = (_("Cannot find device number for volume %s")
|
exception_message = (_("Cannot find device number for volume "
|
||||||
% volume['name'])
|
"%(volumeName)s")
|
||||||
|
% {'volumeName': volume['name']})
|
||||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||||
|
|
||||||
device_number = device_info['hostlunid']
|
device_number = device_info['hostlunid']
|
||||||
storage_system = device_info['storagesystem']
|
|
||||||
|
|
||||||
# sp is "SP_A" or "SP_B"
|
LOG.info(_(
|
||||||
sp = device_info['owningsp']
|
"location is: %(location)s") % {'location': location})
|
||||||
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)
|
|
||||||
|
|
||||||
foundEndpoint = False
|
|
||||||
for loc in location:
|
for loc in location:
|
||||||
results = loc.split(" ")
|
results = loc.split(" ")
|
||||||
properties['target_portal'] = results[0].split(",")[0]
|
properties['target_portal'] = results[0].split(",")[0]
|
||||||
properties['target_iqn'] = results[1]
|
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['target_lun'] = device_number
|
||||||
|
|
||||||
properties['volume_id'] = volume['id']
|
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 'provider_auth' in volume:
|
||||||
if auth:
|
auth = volume['provider_auth']
|
||||||
(auth_method, auth_username, auth_secret) = auth.split()
|
LOG.info(_("AUTH properties: %(authProps)s")
|
||||||
|
% {'authProps': auth})
|
||||||
|
|
||||||
properties['auth_method'] = auth_method
|
if auth is not None:
|
||||||
properties['auth_username'] = auth_username
|
(auth_method, auth_username, auth_secret) = auth.split()
|
||||||
properties['auth_password'] = auth_secret
|
|
||||||
|
properties['auth_method'] = auth_method
|
||||||
|
properties['auth_username'] = auth_username
|
||||||
|
properties['auth_password'] = auth_secret
|
||||||
|
|
||||||
|
LOG.info(_("AUTH properties: %s") % (properties))
|
||||||
|
|
||||||
return properties
|
return properties
|
||||||
|
|
||||||
@ -277,8 +260,32 @@ class EMCSMISISCSIDriver(driver.ISCSIDriver):
|
|||||||
"""Retrieve stats info from volume group."""
|
"""Retrieve stats info from volume group."""
|
||||||
LOG.debug("Updating volume stats")
|
LOG.debug("Updating volume stats")
|
||||||
data = self.common.update_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['storage_protocol'] = 'iSCSI'
|
||||||
data['driver_version'] = self.VERSION
|
data['driver_version'] = self.VERSION
|
||||||
self._stats = data
|
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)
|
1398
cinder/volume/drivers/emc/emc_vmax_masking.py
Normal file
1398
cinder/volume/drivers/emc/emc_vmax_masking.py
Normal file
File diff suppressed because it is too large
Load Diff
649
cinder/volume/drivers/emc/emc_vmax_provision.py
Normal file
649
cinder/volume/drivers/emc/emc_vmax_provision.py
Normal file
@ -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
|
1187
cinder/volume/drivers/emc/emc_vmax_utils.py
Normal file
1187
cinder/volume/drivers/emc/emc_vmax_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1089,10 +1089,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)
|
# value)
|
||||||
#cinder_emc_config_file=/etc/cinder/cinder_emc_config.xml
|
#cinder_emc_config_file=/etc/cinder/cinder_emc_config.xml
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user