VMAX driver - Storage assisted volume migration.

Facilitates retyping from one volume type to another
where volume types belong to the same backend.  Both
All Flash and Hybrid arrays are supported. VMAX2 is
not supported.

Change-Id: Ie4f52afd82867f60556d8fc95d2af7c0f87cee51
Implements: blueprint vmax-volume-migration
This commit is contained in:
Helen Walsh 2016-11-15 17:52:54 +00:00
parent 02389a1d2a
commit 6624c3197b
7 changed files with 970 additions and 917 deletions

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,11 @@ import os.path
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import units
import re
import six
from cinder import exception
from cinder import utils as cinder_utils
from cinder.i18n import _, _LE, _LI, _LW
from cinder.objects import fields
from cinder.volume.drivers.emc import emc_vmax_fast
@ -56,6 +58,7 @@ ARRAY = 'storagetype:array'
FASTPOLICY = 'storagetype:fastpolicy'
BACKENDNAME = 'volume_backend_name'
COMPOSITETYPE = 'storagetype:compositetype'
MULTI_POOL_SUPPORT = 'MultiPoolSupport'
STRIPECOUNT = 'storagetype:stripecount'
MEMBERCOUNT = 'storagetype:membercount'
STRIPED = 'striped'
@ -79,7 +82,11 @@ emc_opts = [
cfg.StrOpt('cinder_emc_config_file',
default=CINDER_EMC_CONFIG_FILE,
help='use this file for cinder emc plugin '
'config data'), ]
'config data'),
cfg.StrOpt('multi_pool_support',
default=False,
help='use this value to specify'
'multi-pool support for VMAX3')]
CONF.register_opts(emc_opts)
@ -128,6 +135,7 @@ class EMCVMAXCommon(object):
self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl)
self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl)
self.version = version
self.multiPoolSupportEnabled = False
self._gather_info()
def _gather_info(self):
@ -138,7 +146,11 @@ class EMCVMAXCommon(object):
else:
self.pool_info['config_file'] = (
self.configuration.safe_get('cinder_emc_config_file'))
if hasattr(self.configuration, 'multi_pool_support'):
tempMultiPoolSupported = cinder_utils.get_bool_param(
'multi_pool_support', self.configuration)
if tempMultiPoolSupported:
self.multiPoolSupportEnabled = True
self.pool_info['backend_name'] = (
self.configuration.safe_get('volume_backend_name'))
self.pool_info['max_over_subscription_ratio'] = (
@ -151,9 +163,75 @@ class EMCVMAXCommon(object):
{'emcConfigFileName': self.pool_info['config_file'],
'backendName': self.pool_info['backend_name']})
self.pool_info['arrays_info'] = (
self.utils.parse_file_to_get_array_map(
self.pool_info['config_file']))
arrayInfoList = self.utils.parse_file_to_get_array_map(
self.pool_info['config_file'])
# Assuming that there is a single array info object always
# Check if Multi pool support is enabled
if self.multiPoolSupportEnabled is False:
self.pool_info['arrays_info'] = arrayInfoList
else:
finalArrayInfoList = self._get_slo_workload_combinations(
arrayInfoList)
self.pool_info['arrays_info'] = finalArrayInfoList
def _get_slo_workload_combinations(self, arrayInfoList):
"""Method to query the array for SLO and Workloads.
Takes the arrayInfoList object and generates a set which has
all available SLO & Workload combinations
:param arrayInfoList:
:return: finalArrayInfoList
:raises: Exception
"""
try:
sloWorkloadSet = set()
# Pattern for extracting the SLO & Workload String
pattern = re.compile("^-S[A-Z]+")
for arrayInfo in arrayInfoList:
self._set_ecom_credentials(arrayInfo)
isV3 = self.utils.isArrayV3(self.conn,
arrayInfo['SerialNumber'])
# Only if the array is VMAX3
if isV3:
poolInstanceName, storageSystemStr = (
self._find_pool_in_array(arrayInfo['SerialNumber'],
arrayInfo['PoolName'], isV3))
# Get the pool capability
storagePoolCapability = (
self.provisionv3.get_storage_pool_capability(
self.conn, poolInstanceName))
# Get the pool settings
storagePoolSettings = self.conn.AssociatorNames(
storagePoolCapability,
ResultClass='CIM_storageSetting')
for storagePoolSetting in storagePoolSettings:
settingInstanceID = storagePoolSetting['InstanceID']
settingInstanceDetails = settingInstanceID.split('+')
sloWorkloadString = settingInstanceDetails[2]
if pattern.match(sloWorkloadString):
length = len(sloWorkloadString)
tempSloWorkloadString = (
sloWorkloadString[2:length - 1])
sloWorkloadSet.add(tempSloWorkloadString)
# Assuming that there is always a single arrayInfo object
finalArrayInfoList = []
for sloWorkload in sloWorkloadSet:
# Doing a shallow copy will work as we are modifying
# only strings
temparrayInfo = arrayInfoList[0].copy()
slo, workload = sloWorkload.split(':')
if temparrayInfo['SLO'] is None:
temparrayInfo['SLO'] = slo
temparrayInfo['Workload'] = workload
finalArrayInfoList.append(temparrayInfo)
except Exception:
exceptionMessage = (_(
"Unable to get the SLO/Workload combinations from the array"))
LOG.exception(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
return finalArrayInfoList
def create_volume(self, volume):
"""Creates a EMC(VMAX) volume from a pre-existing storage pool.
@ -230,14 +308,12 @@ class EMCVMAXCommon(object):
:raises: VolumeBackendAPIException
"""
LOG.debug("Entering create_volume_from_snapshot.")
snapshot['host'] = volume['host']
extraSpecs = self._initial_setup(snapshot)
extraSpecs = self._initial_setup(snapshot, host=volume['host'])
self.conn = self._get_ecom_connection()
snapshotInstance = self._find_lun(snapshot)
self._sync_check(snapshotInstance, snapshot['name'], extraSpecs)
snapshot['host'] = volume['host']
return self._create_cloned_volume(volume, snapshot, extraSpecs, False)
def create_cloned_volume(self, cloneVolume, sourceVolume):
@ -285,8 +361,7 @@ class EMCVMAXCommon(object):
"""
LOG.info(_LI("Delete Snapshot: %(snapshotName)s."),
{'snapshotName': snapshot['name']})
snapshot['host'] = volume['host']
self._delete_snapshot(snapshot)
self._delete_snapshot(snapshot, volume['host'])
def _remove_members(self, controllerConfigService,
volumeInstance, connector, extraSpecs):
@ -662,6 +737,10 @@ class EMCVMAXCommon(object):
def update_volume_stats(self):
"""Retrieve stats info."""
pools = []
# Dictionary to hold the VMAX3 arrays for which the SRP details
# have already been queried
# This only applies to the arrays for which WLP is not enabled
arrays = {}
backendName = self.pool_info['backend_name']
max_oversubscription_ratio = (
self.pool_info['max_over_subscription_ratio'])
@ -669,17 +748,43 @@ class EMCVMAXCommon(object):
array_max_over_subscription = None
array_reserve_percent = None
for arrayInfo in self.pool_info['arrays_info']:
alreadyQueried = False
self._set_ecom_credentials(arrayInfo)
# Check what type of array it is
isV3 = self.utils.isArrayV3(self.conn, arrayInfo['SerialNumber'])
isV3 = self.utils.isArrayV3(self.conn,
arrayInfo['SerialNumber'])
if isV3:
(location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb,
array_reserve_percent) = self._update_srp_stats(arrayInfo)
poolName = ("%(slo)s+%(poolName)s+%(array)s"
% {'slo': arrayInfo['SLO'],
'poolName': arrayInfo['PoolName'],
'array': arrayInfo['SerialNumber']})
# Report only the SLO name in the pool name for
# backward compatibility
if self.multiPoolSupportEnabled is False:
(location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb,
array_reserve_percent,
wlpEnabled) = self._update_srp_stats(arrayInfo)
poolName = ("%(slo)s+%(poolName)s+%(array)s"
% {'slo': arrayInfo['SLO'],
'poolName': arrayInfo['PoolName'],
'array': arrayInfo['SerialNumber']})
else:
# Add both SLO & Workload name in the pool name
# Query the SRP only once if WLP is not enabled
# Only insert the array details in the dict once
if arrayInfo['SerialNumber'] not in arrays:
(location_info, total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb,
array_reserve_percent,
wlpEnabled) = self._update_srp_stats(arrayInfo)
else:
alreadyQueried = True
poolName = ("%(slo)s+%(workload)s+%(poolName)s+%(array)s"
% {'slo': arrayInfo['SLO'],
'workload': arrayInfo['Workload'],
'poolName': arrayInfo['PoolName'],
'array': arrayInfo['SerialNumber']})
if wlpEnabled is False:
arrays[arrayInfo['SerialNumber']] = (
[total_capacity_gb, free_capacity_gb,
provisioned_capacity_gb, array_reserve_percent])
else:
# This is V2
(location_info, total_capacity_gb, free_capacity_gb,
@ -689,28 +794,64 @@ class EMCVMAXCommon(object):
% {'poolName': arrayInfo['PoolName'],
'array': arrayInfo['SerialNumber']})
pool = {'pool_name': poolName,
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'provisioned_capacity_gb': provisioned_capacity_gb,
'QoS_support': False,
'location_info': location_info,
'consistencygroup_support': True,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'max_over_subscription_ratio': max_oversubscription_ratio
}
if (alreadyQueried
is True and self.multiPoolSupportEnabled is True):
# The dictionary will only have one key per VMAX3
# Construct the location info
temp_location_info = (
("%(arrayName)s#%(poolName)s#%(slo)s#%(workload)s"
% {'arrayName': arrayInfo['SerialNumber'],
'poolName': arrayInfo['PoolName'],
'slo': arrayInfo['SLO'],
'workload': arrayInfo['Workload']}))
pool = {'pool_name': poolName,
'total_capacity_gb':
arrays[arrayInfo['SerialNumber']][0],
'free_capacity_gb':
arrays[arrayInfo['SerialNumber']][1],
'provisioned_capacity_gb':
arrays[arrayInfo['SerialNumber']][2],
'QoS_support': True,
'location_info': temp_location_info,
'consistencygroup_support': True,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'max_over_subscription_ratio':
max_oversubscription_ratio
}
if (
arrays[arrayInfo['SerialNumber']][3] and
(arrays[arrayInfo['SerialNumber']][3] >
reservedPercentage)):
pool['reserved_percentage'] = (
arrays[arrayInfo['SerialNumber']][3])
else:
pool['reserved_percentage'] = reservedPercentage
else:
pool = {'pool_name': poolName,
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'provisioned_capacity_gb': provisioned_capacity_gb,
'QoS_support': False,
'location_info': location_info,
'consistencygroup_support': True,
'thin_provisioning_support': True,
'thick_provisioning_support': False,
'max_over_subscription_ratio':
max_oversubscription_ratio
}
if (
array_reserve_percent and
(array_reserve_percent > reservedPercentage)):
pool['reserved_percentage'] = array_reserve_percent
else:
pool['reserved_percentage'] = reservedPercentage
if array_max_over_subscription:
pool['max_over_subscription_ratio'] = (
self.utils.override_ratio(
max_oversubscription_ratio,
array_max_over_subscription))
if array_reserve_percent and (
array_reserve_percent > reservedPercentage):
pool['reserved_percentage'] = array_reserve_percent
else:
pool['reserved_percentage'] = reservedPercentage
pools.append(pool)
data = {'vendor_name': "EMC",
@ -736,10 +877,11 @@ class EMCVMAXCommon(object):
:returns: remainingManagedSpaceGbs
:returns: provisionedManagedSpaceGbs
:returns: array_reserve_percent
:returns: wlpEnabled
"""
(totalManagedSpaceGbs, remainingManagedSpaceGbs,
provisionedManagedSpaceGbs, array_reserve_percent) = (
provisionedManagedSpaceGbs, array_reserve_percent, wlpEnabled) = (
self.provisionv3.get_srp_pool_stats(self.conn, arrayInfo))
LOG.info(_LI(
@ -761,7 +903,7 @@ class EMCVMAXCommon(object):
return (location_info, totalManagedSpaceGbs,
remainingManagedSpaceGbs, provisionedManagedSpaceGbs,
array_reserve_percent)
array_reserve_percent, wlpEnabled)
def retype(self, ctxt, volume, new_type, diff, host):
"""Migrate volume to another host using retype.
@ -1338,14 +1480,31 @@ class EMCVMAXCommon(object):
extraSpecs = self.utils.get_volumetype_extraspecs(volume, volumeTypeId)
qosSpecs = self.utils.get_volumetype_qosspecs(volume, volumeTypeId)
configGroup = None
# If there are no extra specs then the default case is assumed.
if extraSpecs:
configGroup = self.configuration.config_group
configurationFile = self._register_config_file_from_config_group(
configGroup)
self.multiPoolSupportEnabled = (
self._get_multi_pool_support_enabled_flag())
extraSpecs[MULTI_POOL_SUPPORT] = self.multiPoolSupportEnabled
return extraSpecs, configurationFile, qosSpecs
def _get_multi_pool_support_enabled_flag(self):
"""Reads the configuration fpr multi pool support flag.
:returns: MultiPoolSupportEnabled flag
"""
confString = (
self.configuration.safe_get('multi_pool_support'))
retVal = False
stringTrue = "True"
if confString:
if confString.lower() == stringTrue.lower():
retVal = True
return retVal
def _get_ecom_connection(self):
"""Get the ecom connection.
@ -1774,7 +1933,7 @@ class EMCVMAXCommon(object):
% {'ip_port': ip_port})
self.conn = self._get_ecom_connection()
def _initial_setup(self, volume, volumeTypeId=None):
def _initial_setup(self, volume, volumeTypeId=None, host=None):
"""Necessary setup to accumulate the relevant information.
The volume object has a host in which we can parse the
@ -1794,13 +1953,17 @@ class EMCVMAXCommon(object):
extraSpecs, configurationFile, qosSpecs = (
self._set_config_file_and_get_extra_specs(
volume, volumeTypeId))
pool = self._validate_pool(volume)
pool = self._validate_pool(volume, extraSpecs=extraSpecs,
host=host)
LOG.debug("Pool returned is %(pool)s.",
{'pool': pool})
arrayInfo = self.utils.parse_file_to_get_array_map(
configurationFile)
poolRecord = self.utils.extract_record(arrayInfo, pool)
if arrayInfo is not None:
if extraSpecs['MultiPoolSupport'] is True:
poolRecord = arrayInfo[0]
else:
poolRecord = self.utils.extract_record(arrayInfo, pool)
if not poolRecord:
exceptionMessage = (_(
@ -2148,7 +2311,6 @@ class EMCVMAXCommon(object):
'sourceName': sourceName})
self.conn = self._get_ecom_connection()
sourceInstance = self._find_lun(sourceVolume)
storageSystem = sourceInstance['SystemName']
repServCapabilityInstanceName = (
@ -2267,7 +2429,7 @@ class EMCVMAXCommon(object):
cloneDict, cloneName, storageConfigService, storageSystemName,
fastPolicyName, extraSpecs)
def _delete_volume(self, volume):
def _delete_volume(self, volume, host=None):
"""Helper function to delete the specified volume.
:param volume: volume object to be deleted
@ -2278,7 +2440,7 @@ class EMCVMAXCommon(object):
rc = -1
errorRet = (rc, volumeName)
extraSpecs = self._initial_setup(volume)
extraSpecs = self._initial_setup(volume, host=host)
self.conn = self._get_ecom_connection()
volumeInstance = self._find_lun(volume)
@ -2454,7 +2616,7 @@ class EMCVMAXCommon(object):
return numVolumesMapped
def _delete_snapshot(self, snapshot):
def _delete_snapshot(self, snapshot, host=None):
"""Helper function to delete the specified snapshot.
:param snapshot: snapshot object to be deleted
@ -2465,7 +2627,7 @@ class EMCVMAXCommon(object):
self.conn = self._get_ecom_connection()
# Delete the target device.
rc, snapshotname = self._delete_volume(snapshot)
rc, snapshotname = self._delete_volume(snapshot, host)
LOG.info(_LI("Leaving delete_snapshot: %(ssname)s Return code: "
"%(rc)lu."),
{'ssname': snapshotname,
@ -2486,7 +2648,7 @@ class EMCVMAXCommon(object):
cgName = self._update_consistency_group_name(group)
volumeTypeId = group['volume_type_id'].replace(",", "")
extraSpecs = self._initial_setup(None, volumeTypeId)
extraSpecs = self._initial_setup(None, volumeTypeId=volumeTypeId)
_poolInstanceName, storageSystem = (
self._get_pool_and_storage_system(extraSpecs))
@ -2522,8 +2684,8 @@ class EMCVMAXCommon(object):
{'group': group['id']})
cgName = self._update_consistency_group_name(group)
modelUpdate = {}
volumes_model_update = {}
if not self.conn:
self.conn = self._get_ecom_connection()
@ -2547,7 +2709,6 @@ class EMCVMAXCommon(object):
memberInstanceNames = self._get_members_of_replication_group(
cgInstanceName)
self.provision.delete_consistency_group(self.conn,
replicationService,
cgInstanceName, cgName,
@ -3177,23 +3338,9 @@ class EMCVMAXCommon(object):
"belonging to any storage group."),
{'volumeName': volumeName})
else:
self.provision.remove_device_from_storage_group(
self.conn,
controllerConfigService,
foundStorageGroupInstanceName,
volumeInstance.path,
volumeName, extraSpecs)
# Check that it has been removed.
sgFromVolRemovedInstanceName = (
self.utils.wrap_get_storage_group_from_volume(
self.conn, volumeInstance.path, defaultSgName))
if sgFromVolRemovedInstanceName is not None:
LOG.error(_LE(
"Volume : %(volumeName)s has not been "
"removed from source storage group %(storageGroup)s."),
{'volumeName': volumeName,
'storageGroup': sgFromVolRemovedInstanceName})
return False
self.masking.remove_and_reset_members(
self.conn, controllerConfigService, volumeInstance,
volumeName, extraSpecs, None, False)
storageGroupName = self.utils.get_v3_storage_group_name(
poolName, targetSlo, targetWorkload)
@ -3385,8 +3532,33 @@ class EMCVMAXCommon(object):
:param poolRecord: pool record
:returns: dict -- the extra specifications dictionary
"""
extraSpecs[SLO] = poolRecord['SLO']
extraSpecs[WORKLOAD] = poolRecord['Workload']
if extraSpecs['MultiPoolSupport'] is True:
sloFromExtraSpec = None
workloadFromExtraSpec = None
if 'pool_name' in extraSpecs:
try:
poolDetails = extraSpecs['pool_name'].split('+')
sloFromExtraSpec = poolDetails[0]
workloadFromExtraSpec = poolDetails[1]
except KeyError:
LOG.error(_LE("Error parsing SLO, workload from "
"the provided extra_specs."))
else:
# Throw an exception as it is compulsory to have
# pool_name in the extra specs
exceptionMessage = (_(
"Pool_name is not present in the extraSpecs "
"and MultiPoolSupport is enabled"))
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
# If MultiPoolSupport is enabled, we completely
# ignore any entry for SLO & Workload in the poolRecord
extraSpecs[SLO] = sloFromExtraSpec
extraSpecs[WORKLOAD] = workloadFromExtraSpec
else:
extraSpecs[SLO] = poolRecord['SLO']
extraSpecs[WORKLOAD] = poolRecord['Workload']
extraSpecs[ISV3] = True
extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord)
LOG.debug("Pool is: %(pool)s "
@ -4021,7 +4193,7 @@ class EMCVMAXCommon(object):
extraSpecs)
return rc
def _validate_pool(self, volume):
def _validate_pool(self, volume, extraSpecs=None, host=None):
"""Get the pool from volume['host'].
There may be backward compatibiliy concerns, so putting in a
@ -4030,6 +4202,7 @@ class EMCVMAXCommon(object):
assume it was created pre 'Pool Aware Scheduler' feature.
:param volume: the volume Object
:param extraSpecs: extraSpecs provided in the volume type
:returns: string -- pool
:raises: VolumeBackendAPIException
"""
@ -4038,6 +4211,9 @@ class EMCVMAXCommon(object):
if volume is None:
return pool
if host is None:
host = volume['host']
# This check is for all operations except a create.
# On a create provider_location is None
try:
@ -4049,10 +4225,21 @@ class EMCVMAXCommon(object):
except KeyError:
return pool
try:
pool = volume_utils.extract_host(volume['host'], 'pool')
pool = volume_utils.extract_host(host, 'pool')
if pool:
LOG.debug("Pool from volume['host'] is %(pool)s.",
{'pool': pool})
# Check if it matches with the poolname if it is provided
# in the extra specs
if extraSpecs is not None:
if 'pool_name' in extraSpecs:
if extraSpecs['pool_name'] != pool:
exceptionMessage = (_(
"Pool from volume['host'] %(host)s doesn't"
" match with pool_name in extraSpecs.")
% {'host': volume['host']})
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
else:
exceptionMessage = (_(
"Pool from volume['host'] %(host)s not found.")

View File

@ -72,6 +72,8 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
- QoS support (blueprint vmax-qos)
2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot)
- MVs and SGs not reflecting correct protocol (bug #1640222)
- Storage assisted volume migration via retype
(bp vmax-volume-migration)
"""

View File

@ -78,6 +78,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
https://blueprints.launchpad.net/cinder/+spec/vmax-iscsi-multipath
2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot)
- MVs and SGs not reflecting correct protocol (bug #1640222)
- Storage assisted volume migration via retype
(bp vmax-volume-migration)
"""

View File

@ -706,11 +706,13 @@ class EMCVMAXProvisionV3(object):
:returns: remainingCapacityGb
:returns: subscribedCapacityGb
:returns: array_reserve_percent
:returns: wlpEnabled
"""
totalCapacityGb = -1
remainingCapacityGb = -1
subscribedCapacityGb = -1
array_reserve_percent = -1
wlpEnabled = False
storageSystemInstanceName = self.utils.find_storageSystem(
conn, arrayInfo['SerialNumber'])
@ -756,6 +758,7 @@ class EMCVMAXProvisionV3(object):
storageSystemInstanceName['Name']))
if remainingSLOCapacityGb != -1:
remainingCapacityGb = remainingSLOCapacityGb
wlpEnabled = True
else:
LOG.warning(_LW(
"Remaining capacity %(remainingCapacityGb)s "
@ -765,7 +768,7 @@ class EMCVMAXProvisionV3(object):
{'remainingCapacityGb': remainingCapacityGb})
return (totalCapacityGb, remainingCapacityGb, subscribedCapacityGb,
array_reserve_percent)
array_reserve_percent, wlpEnabled)
def _get_remaining_slo_capacity_wlp(self, conn, srpPoolInstanceName,
arrayInfo, systemName):

View File

@ -1260,13 +1260,12 @@ class EMCVMAXUtils(object):
:returns: foundSyncInstanceName
"""
foundSyncInstanceName = None
syncInstanceNames = conn.EnumerateInstanceNames(
'SE_StorageSynchronized_SV_SV')
syncInstanceNames = conn.ReferenceNames(
volumeInstance.path,
ResultClass='SE_StorageSynchronized_SV_SV')
for syncInstanceName in syncInstanceNames:
syncSvTarget = syncInstanceName['SyncedElement']
syncSvSource = syncInstanceName['SystemElement']
if storageSystem != syncSvTarget['SystemName']:
continue
if syncSvTarget['DeviceID'] == volumeInstance['DeviceID'] or (
syncSvSource['DeviceID'] == volumeInstance['DeviceID']):
# Check that it hasn't recently been deleted.
@ -1902,65 +1901,10 @@ class EMCVMAXUtils(object):
return kwargs
def _multi_pool_support(self, fileName):
"""Multi pool support.
<EMC>
<EcomServers>
<EcomServer>
<EcomServerIp>10.108.246.202</EcomServerIp>
...
<Arrays>
<Array>
<SerialNumber>000198700439</SerialNumber>
...
<Pools>
<Pool>
<PoolName>FC_SLVR1</PoolName>
...
</Pool>
</Pools>
</Array>
</Arrays>
</EcomServer>
</EcomServers>
</EMC>
:param fileName: the configuration file
:returns: list
"""
myList = []
connargs = {}
myFile = open(fileName, 'r')
data = myFile.read()
myFile.close()
dom = minidom.parseString(data)
interval = self._process_tag(dom, 'Interval')
retries = self._process_tag(dom, 'Retries')
try:
ecomElements = dom.getElementsByTagName('EcomServer')
if ecomElements and len(ecomElements) > 0:
for ecomElement in ecomElements:
connargs = self._get_connection_info(ecomElement)
arrayElements = ecomElement.getElementsByTagName('Array')
if arrayElements and len(arrayElements) > 0:
for arrayElement in arrayElements:
myList = self._get_pool_info(arrayElement,
fileName, connargs,
interval, retries,
myList)
else:
LOG.error(_LE(
"Please check your xml for format or syntax "
"errors. Please see documentation for more "
"details."))
except IndexError:
pass
return myList
def _single_pool_support(self, fileName):
"""Single pool support.
VMAX2
<EMC>
<EcomServerIp>10.108.246.202</EcomServerIp>
<EcomServerPort>5988</EcomServerPort>
@ -1972,6 +1916,7 @@ class EMCVMAXUtils(object):
<Array>000198700439</Array>
<Pool>FC_SLVR1</Pool>
</EMC>
VMAX3
:param fileName: the configuration file
:returns: list
@ -2021,12 +1966,70 @@ class EMCVMAXUtils(object):
:param fileName: the path and name of the file
:returns: list
"""
# Multi-pool support.
myList = self._multi_pool_support(fileName)
if len(myList) == 0:
myList = self._single_pool_support(fileName)
Sample VMAX2 XML file
<EMC>
<EcomServerIp>10.108.246.202</EcomServerIp>
<EcomServerPort>5988</EcomServerPort>
<EcomUserName>admin</EcomUserName>
<EcomPassword>#1Password</EcomPassword>
<PortGroups>
<PortGroup>OS-PORTGROUP1-PG</PortGroup>
</PortGroups>
<Array>000198700439</Array>
<Pool>FC_SLVR1</Pool>
</EMC>
Sample VMAX3 XML file
<EMC>
<EcomServerIp>10.108.246.202</EcomServerIp>
<EcomServerPort>5988</EcomServerPort>
<EcomUserName>admin</EcomUserName>
<EcomPassword>#1Password</EcomPassword>
<PortGroups>
<PortGroup>OS-PORTGROUP1-PG</PortGroup>
</PortGroups>
<Array>000198700439</Array>
<Pool>FC_SLVR1</Pool>
<ServiceLevel>Diamond</ServiceLevel> <--This is optional
<Workload>OLTP</Workload> <--This is optional
</EMC>
:param fileName: the configuration file
:returns: list
"""
myList = []
kwargs = {}
connargs = {}
with open(fileName, 'r') as my_file:
data = my_file.read()
my_file.close()
dom = minidom.parseString(data)
try:
connargs = self._get_connection_info(dom)
interval = self._process_tag(dom, 'Interval')
retries = self._process_tag(dom, 'Retries')
portGroup = self._get_random_portgroup(dom)
serialNumber = self._process_tag(dom, 'Array')
if serialNumber is None:
LOG.error(_LE(
"Array Serial Number must be in the file "
"%(fileName)s."),
{'fileName': fileName})
poolName = self._process_tag(dom, 'Pool')
if poolName is None:
LOG.error(_LE(
"PoolName must be in the file "
"%(fileName)s."),
{'fileName': fileName})
kwargs = self._fill_record(
connargs, serialNumber, poolName, portGroup, dom)
if interval:
kwargs['Interval'] = interval
if retries:
kwargs['Retries'] = retries
myList.append(kwargs)
except IndexError:
pass
return myList
def extract_record(self, arrayInfo, pool):

View File

@ -0,0 +1,5 @@
---
features:
- Storage assisted volume migration from one Pool/SLO/Workload combination
to another, on the same array, via retype, for the VMAX driver. Both
All Flash and Hybrid VMAX3 arrays are supported. VMAX2 is not supported.