cinder/cinder/volume/drivers/dell_emc/vmax/masking.py

2820 lines
127 KiB
Python

# Copyright (c) 2012 - 2015 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
import six
from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder.volume.drivers.dell_emc.vmax import fast
from cinder.volume.drivers.dell_emc.vmax import provision
from cinder.volume.drivers.dell_emc.vmax import provision_v3
from cinder.volume.drivers.dell_emc.vmax import utils
LOG = logging.getLogger(__name__)
STORAGEGROUPTYPE = 4
POSTGROUPTYPE = 3
INITIATORGROUPTYPE = 2
ISCSI = 'iscsi'
FC = 'fc'
EMC_ROOT = 'root/emc'
FASTPOLICY = 'storagetype:fastpolicy'
ISV3 = 'isV3'
class VMAXMasking(object):
"""Masking class for SMI-S based EMC volume drivers.
Masking code to dynamically create a masking view
This masking class is for EMC volume drivers based on SMI-S.
It supports VMAX arrays.
"""
def __init__(self, prtcl):
self.protocol = prtcl
self.utils = utils.VMAXUtils(prtcl)
self.fast = fast.VMAXFast(prtcl)
self.provision = provision.VMAXProvision(prtcl)
self.provisionv3 = provision_v3.VMAXProvisionV3(prtcl)
def setup_masking_view(self, conn, maskingViewDict, extraSpecs):
@coordination.synchronized("emc-mv-{maskingViewDict[maskingViewName]}")
def do_get_or_create_masking_view_and_map_lun(maskingViewDict):
return self.get_or_create_masking_view_and_map_lun(conn,
maskingViewDict,
extraSpecs)
return do_get_or_create_masking_view_and_map_lun(
maskingViewDict)
def get_or_create_masking_view_and_map_lun(self, conn, maskingViewDict,
extraSpecs):
"""Get or Create a masking view and add a volume to the storage group.
Given a masking view tuple either get or create a masking view and add
the volume to the associated storage group.
If it is a live migration operation then we do not need to remove
the volume from any storage group (default or otherwise).
:param conn: the connection to ecom
:param maskingViewDict: the masking view dict
:param extraSpecs: additional info
:returns: dict -- rollbackDict
:raises VolumeBackendAPIException:
"""
rollbackDict = {}
controllerConfigService = maskingViewDict['controllerConfigService']
volumeInstance = maskingViewDict['volumeInstance']
maskingViewName = maskingViewDict['maskingViewName']
volumeName = maskingViewDict['volumeName']
isV3 = maskingViewDict['isV3']
isLiveMigration = maskingViewDict['isLiveMigration']
maskingViewDict['extraSpecs'] = extraSpecs
defaultStorageGroupInstanceName = None
fastPolicyName = None
storageGroupInstanceName = None
if isLiveMigration is False:
if isV3:
defaultStorageGroupInstanceName = (
self._get_v3_default_storagegroup_instancename(
conn, volumeInstance, maskingViewDict,
controllerConfigService, volumeName))
else:
fastPolicyName = maskingViewDict['fastPolicy']
# If FAST is enabled remove the volume from the default SG.
if fastPolicyName is not None:
defaultStorageGroupInstanceName = (
self._get_and_remove_from_storage_group_v2(
conn, controllerConfigService,
volumeInstance.path,
volumeName, fastPolicyName,
extraSpecs))
else:
# Live Migration
self.remove_and_reset_members(
conn, controllerConfigService, volumeInstance, volumeName,
extraSpecs, maskingViewDict['connector'], False)
# If anything has gone wrong with the masking view we rollback
try:
maskingViewInstanceName, storageGroupInstanceName, errorMessage = (
self._validate_masking_view(conn, maskingViewDict,
defaultStorageGroupInstanceName,
extraSpecs))
LOG.debug(
"The masking view in the attach operation is "
"%(maskingViewInstanceName)s. The storage group "
"in the masking view is %(storageGroupInstanceName)s.",
{'maskingViewInstanceName': maskingViewInstanceName,
'storageGroupInstanceName': storageGroupInstanceName})
except Exception as e:
LOG.exception(
"Masking View creation or retrieval was not successful "
"for masking view %(maskingViewName)s. "
"Attempting rollback.",
{'maskingViewName': maskingViewDict['maskingViewName']})
errorMessage = e
rollbackDict['pgGroupName'], pg_errorMessage = (
self._get_port_group_name_from_mv(
conn, maskingViewDict['maskingViewName'],
maskingViewDict['storageSystemName']))
if pg_errorMessage:
errorMessage = pg_errorMessage
if not errorMessage:
# Only after the masking view has been validated, add the
# volume to the storage group and recheck that it has been
# successfully added.
errorMessage = self._check_adding_volume_to_storage_group(
conn, maskingViewDict, storageGroupInstanceName)
rollbackDict['controllerConfigService'] = controllerConfigService
rollbackDict['defaultStorageGroupInstanceName'] = (
defaultStorageGroupInstanceName)
rollbackDict['volumeInstance'] = volumeInstance
rollbackDict['volumeName'] = volumeName
rollbackDict['fastPolicyName'] = fastPolicyName
rollbackDict['isV3'] = isV3
rollbackDict['extraSpecs'] = extraSpecs
rollbackDict['sgGroupName'] = maskingViewDict['sgGroupName']
rollbackDict['igGroupName'] = maskingViewDict['igGroupName']
rollbackDict['connector'] = maskingViewDict['connector']
if errorMessage:
# Rollback code if we cannot complete any of the steps above
# successfully then we must roll back by adding the volume back to
# the default storage group for that fast policy.
if (fastPolicyName is not None):
# If the errorMessage was returned before the volume
# was removed from the default storage group no action.
self._check_if_rollback_action_for_masking_required(
conn, rollbackDict)
if isV3:
if maskingViewDict['slo'] is not None:
rollbackDict['storageSystemName'] = (
maskingViewDict['storageSystemName'])
rollbackDict['slo'] = maskingViewDict['slo']
self._check_if_rollback_action_for_masking_required(
conn, rollbackDict)
else:
errorMessage = self._check_adding_volume_to_storage_group(
conn, rollbackDict,
rollbackDict['defaultStorageGroupInstanceName'])
if errorMessage:
LOG.error(errorMessage)
exceptionMessage = (_(
"Failed to get, create or add volume %(volumeName)s "
"to masking view %(maskingViewName)s. "
"The error message received was %(errorMessage)s.")
% {'maskingViewName': maskingViewName,
'volumeName': volumeName,
'errorMessage': errorMessage})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(data=exceptionMessage)
return rollbackDict
def _get_v3_default_storagegroup_instancename(self, conn, volumeinstance,
maskingviewdict,
controllerConfigService,
volumeName):
defaultStorageGroupInstanceName = None
defaultSgGroupName = self.utils.get_v3_storage_group_name(
maskingviewdict['pool'],
maskingviewdict['slo'],
maskingviewdict['workload'],
maskingviewdict['isCompressionDisabled'],
maskingviewdict['replication_enabled'])
assocStorageGroupInstanceNames = (
self.utils.get_storage_groups_from_volume(
conn, volumeinstance.path))
for assocStorageGroupInstanceName in (
assocStorageGroupInstanceNames):
instance = conn.GetInstance(
assocStorageGroupInstanceName, LocalOnly=False)
assocStorageGroupName = instance['ElementName']
if assocStorageGroupName == defaultSgGroupName:
defaultStorageGroupInstanceName = (
assocStorageGroupInstanceName)
break
if defaultStorageGroupInstanceName:
self._get_and_remove_from_storage_group_v3(
conn, controllerConfigService, volumeinstance.path,
volumeName, maskingviewdict,
defaultStorageGroupInstanceName)
else:
LOG.warning(
"Volume: %(volumeName)s does not belong "
"to storage group %(defaultSgGroupName)s.",
{'volumeName': volumeName,
'defaultSgGroupName': defaultSgGroupName})
return defaultStorageGroupInstanceName
def _validate_masking_view(self, conn, maskingViewDict,
defaultStorageGroupInstanceName,
extraSpecs):
"""Validate all the individual pieces of the masking view.
:param conn: the ecom connection
:param maskingViewDict: the masking view dictionary
:param defaultStorageGroupInstanceName: the default SG
:param extraSpecs: extra specifications
:returns: maskingViewInstanceName
:returns: storageGroupInstanceName,
:returns: string -- errorMessage
"""
storageSystemName = maskingViewDict['storageSystemName']
maskingViewName = maskingViewDict['maskingViewName']
maskingViewInstanceName = self._find_masking_view(
conn, maskingViewName, storageSystemName)
if maskingViewInstanceName is None:
maskingViewInstanceName, storageGroupInstanceName, errorMessage = (
self._validate_new_masking_view(
conn, maskingViewDict, defaultStorageGroupInstanceName,
extraSpecs))
else:
storageGroupInstanceName, errorMessage = (
self._validate_existing_masking_view(
conn, maskingViewDict, maskingViewInstanceName,
extraSpecs))
return maskingViewInstanceName, storageGroupInstanceName, errorMessage
def _validate_new_masking_view(self, conn, maskingViewDict,
defaultStorageGroupInstanceName,
extraSpecs):
"""Validate the creation of a new masking view.
:param conn: the ecom connection
:param maskingViewDict: the masking view dictionary
:param defaultStorageGroupInstanceName: the default SG
:param extraSpecs: extra specifications
:returns: maskingViewInstanceName
:returns: storageGroupInstanceName,
:returns: string -- errorMessage
"""
controllerConfigService = maskingViewDict['controllerConfigService']
igGroupName = maskingViewDict['igGroupName']
connector = maskingViewDict['connector']
storageSystemName = maskingViewDict['storageSystemName']
maskingViewName = maskingViewDict['maskingViewName']
pgGroupName = maskingViewDict['pgGroupName']
LOG.info("Returning random Port Group: %(portGroupName)s.",
{'portGroupName': pgGroupName})
storageGroupInstanceName, errorMessage = (
self._check_storage_group(
conn, maskingViewDict, defaultStorageGroupInstanceName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
portGroupInstanceName, errorMessage = (
self._check_port_group(conn, controllerConfigService,
pgGroupName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
initiatorGroupInstanceName, errorMessage = (
self._check_initiator_group(conn, controllerConfigService,
igGroupName, connector,
storageSystemName, extraSpecs))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
# Only after the components of the MV have been validated,
# add the volume to the storage group and recheck that it
# has been successfully added. This is necessary before
# creating a new masking view.
errorMessage = self._check_adding_volume_to_storage_group(
conn, maskingViewDict, storageGroupInstanceName)
if errorMessage:
return None, storageGroupInstanceName, errorMessage
maskingViewInstanceName, errorMessage = (
self._check_masking_view(
conn, controllerConfigService,
maskingViewName, storageGroupInstanceName,
portGroupInstanceName, initiatorGroupInstanceName,
extraSpecs))
return maskingViewInstanceName, storageGroupInstanceName, errorMessage
def _validate_existing_masking_view(self,
conn, maskingViewDict,
maskingViewInstanceName, extraSpecs):
"""Validate the components of an existing masking view.
:param conn: the ecom connection
:param maskingViewDict: the masking view dictionary
:param maskingViewInstanceName: the masking view instance name
:param extraSpecs: extra specification
:returns: storageGroupInstanceName
:returns: string -- errorMessage
"""
storageGroupInstanceName = None
controllerConfigService = maskingViewDict['controllerConfigService']
sgGroupName = maskingViewDict['sgGroupName']
igGroupName = maskingViewDict['igGroupName']
connector = maskingViewDict['connector']
storageSystemName = maskingViewDict['storageSystemName']
maskingViewName = maskingViewDict['maskingViewName']
checkInitiator = maskingViewDict['initiatorCheck']
# First verify that the initiator group matches the initiators.
if checkInitiator:
errorMessage = self._check_existing_initiator_group(
conn, controllerConfigService, maskingViewName,
connector, storageSystemName, igGroupName, extraSpecs)
if errorMessage:
return storageGroupInstanceName, errorMessage
storageGroupInstanceName, errorMessage = (
self._check_existing_storage_group(
conn, controllerConfigService, sgGroupName,
maskingViewInstanceName))
return storageGroupInstanceName, errorMessage
def _check_storage_group(self, conn,
maskingViewDict, storageGroupInstanceName):
"""Get the storage group and return it.
:param conn: the ecom connection
:param maskingViewDict: the masking view dictionary
:param storageGroupInstanceName: default storage group instance name
:returns: storageGroupInstanceName
:returns: string -- msg, the error message
"""
msg = None
storageGroupInstanceName = (
self._get_storage_group_instance_name(
conn, maskingViewDict, storageGroupInstanceName))
if storageGroupInstanceName is None:
# This may be used in exception hence the use of _.
msg = (_(
"Cannot get or create a storage group: %(sgGroupName)s"
" for volume %(volumeName)s ") %
{'sgGroupName': maskingViewDict['sgGroupName'],
'volumeName': maskingViewDict['volumeName']})
LOG.error(msg)
return storageGroupInstanceName, msg
def _check_existing_storage_group(
self, conn, controllerConfigService,
sgGroupName, maskingViewInstanceName):
"""Check that we can get the existing storage group.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param sgGroupName: the storage group name
:param maskingViewInstanceName: the masking view instance name
:returns: storageGroupInstanceName
:returns: string -- msg, the error message
"""
msg = None
sgFromMvInstanceName = (
self._get_storage_group_from_masking_view_instance(
conn, maskingViewInstanceName))
if sgFromMvInstanceName is None:
# This may be used in exception hence the use of _.
msg = (_(
"Cannot get storage group: %(sgGroupName)s "
"from masking view %(maskingViewInstanceName)s. ") %
{'sgGroupName': sgGroupName,
'maskingViewInstanceName': maskingViewInstanceName})
LOG.error(msg)
return sgFromMvInstanceName, msg
def _check_port_group(self, conn,
controllerConfigService, pgGroupName):
"""Check that you can either get or create a port group.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param pgGroupName: the port group Name
:returns: portGroupInstanceName
:returns: string -- msg, the error message
"""
msg = None
portGroupInstanceName = self._get_port_group_instance_name(
conn, controllerConfigService, pgGroupName)
if portGroupInstanceName is None:
# This may be used in exception hence the use of _.
msg = (_(
"Cannot get port group: %(pgGroupName)s. ") %
{'pgGroupName': pgGroupName})
LOG.error(msg)
return portGroupInstanceName, msg
def _check_initiator_group(
self, conn, controllerConfigService, igGroupName,
connector, storageSystemName, extraSpecs):
"""Check that initiator group can be either retrieved or created.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param igGroupName: the initiator group Name
:param connector: the connector object
:param storageSystemName: the storage system name
:param extraSpecs: extra specifications
:returns: initiatorGroupInstanceName
:returns: string -- the error message
"""
msg = None
initiatorGroupInstanceName = (
self._get_initiator_group_instance_name(
conn, controllerConfigService, igGroupName, connector,
storageSystemName, extraSpecs))
if initiatorGroupInstanceName is None:
# This may be used in exception hence the use of _.
msg = (_(
"Cannot get or create initiator group: "
"%(igGroupName)s. ") %
{'igGroupName': igGroupName})
LOG.error(msg)
return initiatorGroupInstanceName, msg
def _check_existing_initiator_group(
self, conn, controllerConfigService, maskingViewName,
connector, storageSystemName, igGroupName, extraSpecs):
"""Check that existing initiator group in the masking view.
Check if the initiators in the initiator group match those in the
system.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param maskingViewName: the masking view name
:param connector: the connector object
:param storageSystemName: the storage system name
:param igGroupName: the initiator group name
:param extraSpecs: extra specification
:returns: string -- msg, the error message
"""
msg = None
if not self._verify_initiator_group_from_masking_view(
conn, controllerConfigService, maskingViewName,
connector, storageSystemName, igGroupName,
extraSpecs):
# This may be used in exception hence the use of _.
msg = (_(
"Unable to verify initiator group: %(igGroupName)s "
"in masking view %(maskingViewName)s. ") %
{'igGroupName': igGroupName,
'maskingViewName': maskingViewName})
LOG.error(msg)
return msg
def _check_masking_view(
self, conn, controllerConfigService,
maskingViewName, storageGroupInstanceName,
portGroupInstanceName, initiatorGroupInstanceName, extraSpecs):
"""Check that masking view can be either got or created.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param maskingViewName: the masking view name
:param storageGroupInstanceName: storage group instance name
:param portGroupInstanceName: port group instance name
:param initiatorGroupInstanceName: the initiator group instance name
:param extraSpecs: extra specifications
:returns: maskingViewInstanceName
:returns: string -- msg, the error message
"""
msg = None
maskingViewInstanceName = (
self._get_masking_view_instance_name(
conn, controllerConfigService, maskingViewName,
storageGroupInstanceName, portGroupInstanceName,
initiatorGroupInstanceName, extraSpecs))
if maskingViewInstanceName is None:
# This may be used in exception hence the use of _.
msg = (_(
"Cannot create masking view: %(maskingViewName)s. ") %
{'maskingViewName': maskingViewName})
LOG.error(msg)
return maskingViewInstanceName, msg
def _check_adding_volume_to_storage_group(
self, conn, maskingViewDict, storageGroupInstanceName):
"""Add the volume to the storage group and double check it is there.
:param conn: the ecom connection
:param maskingViewDict: the masking view dictionary
:param storageGroupInstanceName: storage group instance name
:returns: string -- the error message
"""
controllerConfigService = maskingViewDict['controllerConfigService']
sgGroupName = maskingViewDict['sgGroupName']
volumeInstance = maskingViewDict['volumeInstance']
volumeName = maskingViewDict['volumeName']
msg = None
if self._is_volume_in_storage_group(
conn, storageGroupInstanceName,
volumeInstance, sgGroupName):
LOG.warning(
"Volume: %(volumeName)s is already part "
"of storage group %(sgGroupName)s.",
{'volumeName': volumeName,
'sgGroupName': sgGroupName})
else:
msg = self._add_volume_to_sg_and_verify(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, sgGroupName,
maskingViewDict['extraSpecs'])
return msg
def _add_volume_to_sg_and_verify(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, sgGroupName, extraSpecs):
"""Add the volume to the storage group and double check it is there.
:param conn: the ecom connection
:param controllerConfigService: controller service
:param storageGroupInstanceName: storage group instance name
:param volumeInstance: the volume instance
:param volumeName: the volume name
:param sgGroupName: the storage group name
:param extraSpecs: the extra specifications
:returns: string -- the error message
"""
msg = None
self.add_volume_to_storage_group(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, sgGroupName, extraSpecs)
if not self._is_volume_in_storage_group(
conn, storageGroupInstanceName, volumeInstance, sgGroupName):
# This may be used in exception hence the use of _.
msg = (_(
"Volume: %(volumeName)s was not added "
"to storage group %(sgGroupName)s.") %
{'volumeName': volumeName,
'sgGroupName': sgGroupName})
LOG.error(msg)
else:
LOG.info("Successfully added %(volumeName)s to %(sgGroupName)s.",
{'volumeName': volumeName,
'sgGroupName': sgGroupName})
return msg
def _get_and_remove_from_storage_group_v2(
self, conn, controllerConfigService, volumeInstanceName,
volumeName, fastPolicyName, extraSpecs):
"""Get the storage group and remove volume from it.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param volumeInstanceName: volume instance name
:param volumeName: volume name
:param fastPolicyName: fast name
:param extraSpecs: additional info
:returns: defaultStorageGroupInstanceName
:raises: VolumeBackendAPIException
"""
defaultStorageGroupInstanceName = (
self.fast.get_and_verify_default_storage_group(
conn, controllerConfigService, volumeInstanceName,
volumeName, fastPolicyName))
if defaultStorageGroupInstanceName is None:
exceptionMessage = (_(
"Cannot get the default storage group for FAST policy: "
"%(fastPolicyName)s.")
% {'fastPolicyName': fastPolicyName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
retStorageGroupInstanceName = (
self.remove_device_from_default_storage_group(
conn, controllerConfigService, volumeInstanceName,
volumeName, fastPolicyName, extraSpecs))
if retStorageGroupInstanceName is None:
exceptionMessage = (_(
"Failed to remove volume %(volumeName)s from default SG.")
% {'volumeName': volumeName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
return defaultStorageGroupInstanceName
def _get_and_remove_from_storage_group_v3(
self, conn, controllerConfigService, volumeInstanceName,
volumeName, maskingViewDict, storageGroupInstanceName):
"""Get the storage group and remove volume from it.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param volumeInstanceName: volume instance name
:param volumeName: volume name
:param maskingViewDict: the masking view dictionary
:param storageGroupInstanceName: storage group instance name
:raises VolumeBackendAPIException:
"""
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage group "
"before removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'volumeName': volumeName})
volInstance = conn.GetInstance(volumeInstanceName, LocalOnly=False)
self._remove_volume_from_sg(
conn, controllerConfigService, storageGroupInstanceName,
volInstance, maskingViewDict['extraSpecs'])
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage group "
"after removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'volumeName': volumeName})
# Required for unit tests.
emptyStorageGroupInstanceName = (
self._wrap_get_storage_group_from_volume(
conn, volumeInstanceName, maskingViewDict['sgGroupName']))
if emptyStorageGroupInstanceName is not None:
exceptionMessage = (_(
"Failed to remove volume %(volumeName)s from default SG: "
"%(volumeName)s.")
% {'volumeName': volumeName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
def _is_volume_in_storage_group(
self, conn, storageGroupInstanceName, volumeInstance, sgName):
"""Check if the volume is already part of the storage group.
Check if the volume is already part of the storage group,
if it is no need to re-add it.
:param conn: the connection to ecom
:param storageGroupInstanceName: the storage group instance name
:param volumeInstance: the volume instance
:param sgName: the storage group name
:returns: boolean
"""
foundStorageGroupInstanceName = (
self.utils.get_storage_group_from_volume(
conn, volumeInstance.path, sgName))
if foundStorageGroupInstanceName is not None:
storageGroupInstance = conn.GetInstance(
storageGroupInstanceName, LocalOnly=False)
LOG.debug(
"The existing storage group instance element name is: "
"%(existingElement)s.",
{'existingElement': storageGroupInstance['ElementName']})
foundStorageGroupInstance = conn.GetInstance(
foundStorageGroupInstanceName, LocalOnly=False)
LOG.debug(
"The found storage group instance element name is: "
"%(foundElement)s.",
{'foundElement': foundStorageGroupInstance['ElementName']})
if (foundStorageGroupInstance['ElementName'] == (
storageGroupInstance['ElementName'])):
return True
return False
def _find_masking_view(self, conn, maskingViewName, storageSystemName):
"""Given the masking view name get the masking view instance.
:param conn: connection to the ecom server
:param maskingViewName: the masking view name
:param storageSystemName: the storage system name(String)
:returns: dict -- foundMaskingViewInstanceName
"""
foundMaskingViewInstanceName = None
storageSystemInstanceName = self.utils.find_storageSystem(
conn, storageSystemName)
maskingViewInstances = conn.Associators(
storageSystemInstanceName,
ResultClass='EMC_LunMaskingSCSIProtocolController')
for maskingViewInstance in maskingViewInstances:
if maskingViewName == maskingViewInstance['ElementName']:
foundMaskingViewInstanceName = maskingViewInstance.path
break
if foundMaskingViewInstanceName is not None:
# Now check that is has not been deleted.
instance = self.utils.get_existing_instance(
conn, foundMaskingViewInstanceName)
if instance is None:
foundMaskingViewInstanceName = None
LOG.error(
"Looks like masking view: %(maskingViewName)s "
"has recently been deleted.",
{'maskingViewName': maskingViewName})
else:
LOG.debug(
"Found existing masking view: %(maskingViewName)s.",
{'maskingViewName': maskingViewName})
return foundMaskingViewInstanceName
def _create_storage_group(
self, conn, maskingViewDict, defaultStorageGroupInstanceName):
"""Create a new storage group that doesn't already exist.
If fastPolicyName is not none we attempt to remove it from the
default storage group of that policy and associate to the new storage
group that will be part of the masking view.
Will not handle any exception in this method it will be handled
up the stack.
:param conn: connection to the ecom server
:param maskingViewDict: the masking view dictionary
:param defaultStorageGroupInstanceName: the default storage group
instance name (Can be None)
:returns: foundStorageGroupInstanceName the instance Name of the
storage group
"""
failedRet = None
controllerConfigService = maskingViewDict['controllerConfigService']
storageGroupName = maskingViewDict['sgGroupName']
isV3 = maskingViewDict['isV3']
if isV3:
workload = maskingViewDict['workload']
pool = maskingViewDict['pool']
slo = maskingViewDict['slo']
foundStorageGroupInstanceName = (
self.provisionv3.create_storage_group_v3(
conn, controllerConfigService, storageGroupName,
pool, slo, workload, maskingViewDict['extraSpecs'],
maskingViewDict['isCompressionDisabled']))
else:
fastPolicyName = maskingViewDict['fastPolicy']
volumeInstance = maskingViewDict['volumeInstance']
foundStorageGroupInstanceName = (
self.provision.create_and_get_storage_group(
conn, controllerConfigService, storageGroupName,
volumeInstance.path, maskingViewDict['extraSpecs']))
if (fastPolicyName is not None and
defaultStorageGroupInstanceName is not None):
assocTierPolicyInstanceName = (
self.fast.add_storage_group_and_verify_tier_policy_assoc(
conn, controllerConfigService,
foundStorageGroupInstanceName,
storageGroupName, fastPolicyName,
maskingViewDict['extraSpecs']))
if assocTierPolicyInstanceName is None:
LOG.error(
"Cannot add and verify tier policy association for "
"storage group : %(storageGroupName)s to "
"FAST policy : %(fastPolicyName)s.",
{'storageGroupName': storageGroupName,
'fastPolicyName': fastPolicyName})
return failedRet
if foundStorageGroupInstanceName is None:
LOG.error(
"Cannot get storage Group from job : %(storageGroupName)s.",
{'storageGroupName': storageGroupName})
return failedRet
else:
LOG.info(
"Created new storage group: %(storageGroupName)s.",
{'storageGroupName': storageGroupName})
return foundStorageGroupInstanceName
def find_port_group(self, conn, controllerConfigService, portGroupName):
"""Given the port Group name get the port group instance name.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param portGroupName: the name of the port group you are getting
:returns: foundPortGroupInstanceName
"""
foundPortGroupInstanceName = None
portMaskingGroupInstances = conn.Associators(
controllerConfigService, ResultClass='CIM_TargetMaskingGroup')
for portMaskingGroupInstance in portMaskingGroupInstances:
if portGroupName == portMaskingGroupInstance['ElementName']:
# Check to see if it has been recently deleted.
instance = self.utils.get_existing_instance(
conn, portMaskingGroupInstance.path)
if instance is None:
foundPortGroupInstanceName = None
else:
foundPortGroupInstanceName = instance.path
break
if foundPortGroupInstanceName is None:
LOG.error(
"Could not find port group : %(portGroupName)s. Check that "
"the EMC configuration file has the correct port group name.",
{'portGroupName': portGroupName})
return foundPortGroupInstanceName
def _create_or_get_initiator_group(
self, conn, controllerConfigService, igGroupName,
connector, storageSystemName, extraSpecs):
"""Attempt to create an initiatorGroup.
If one already exists with the same Initiator/wwns then get it.
Check to see if an initiatorGroup already exists, that matches the
connector information.
NOTE: An initiator/wwn can only belong to one initiatorGroup.
If we were to attempt to create one with an initiator/wwn that
is already belong to another initiatorGroup, it would fail.
:param conn: connection to the ecom server
:param controllerConfigService: the controller config Servicer
:param igGroupName: the proposed name of the initiator group
:param connector: the connector information to the host
:param storageSystemName: the storage system name (String)
:param extraSpecs: extra specifications
:returns: foundInitiatorGroupInstanceName
"""
initiatorNames = self._find_initiator_names(conn, connector)
LOG.debug("The initiator name(s) are: %(initiatorNames)s.",
{'initiatorNames': initiatorNames})
foundInitiatorGroupInstanceName = self._find_initiator_masking_group(
conn, controllerConfigService, initiatorNames)
# If you cannot find an initiatorGroup that matches the connector
# info create a new initiatorGroup.
if foundInitiatorGroupInstanceName is None:
# Check that our connector information matches the
# hardwareId(s) on the vmax.
storageHardwareIDInstanceNames = (
self._get_storage_hardware_id_instance_names(
conn, initiatorNames, storageSystemName))
if not storageHardwareIDInstanceNames:
LOG.info(
"Initiator Name(s) %(initiatorNames)s are not on array "
"%(storageSystemName)s.",
{'initiatorNames': initiatorNames,
'storageSystemName': storageSystemName})
storageHardwareIDInstanceNames = (
self._create_hardware_ids(conn, initiatorNames,
storageSystemName))
if not storageHardwareIDInstanceNames:
msg = (_("Failed to create hardware id(s) on "
"%(storageSystemName)s.")
% {'storageSystemName': storageSystemName})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
foundInitiatorGroupInstanceName = self._create_initiator_Group(
conn, controllerConfigService, igGroupName,
storageHardwareIDInstanceNames, extraSpecs)
LOG.info("Created new initiator group name: %(igGroupName)s.",
{'igGroupName': igGroupName})
else:
initiatorGroupInstance = conn.GetInstance(
foundInitiatorGroupInstanceName, LocalOnly=False)
LOG.info("Using existing initiator group name: %(igGroupName)s.",
{'igGroupName': initiatorGroupInstance['ElementName']})
return foundInitiatorGroupInstanceName
def _find_initiator_names(self, conn, connector):
"""Check the connector object for initiators(ISCSI) or wwpns(FC).
:param conn: the connection to the ecom
:param connector: the connector object
:returns: list -- list of found initiator names
:raises VolumeBackendAPIException:
"""
foundinitiatornames = []
name = 'initiator name'
if (self.protocol.lower() == ISCSI and connector['initiator']):
foundinitiatornames.append(connector['initiator'])
elif self.protocol.lower() == FC:
if ('wwpns' in connector and connector['wwpns']):
for wwn in connector['wwpns']:
foundinitiatornames.append(wwn)
name = 'world wide port names'
else:
msg = (_("FC is the protocol but wwpns are "
"not supplied by OpenStack."))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if (foundinitiatornames is None or len(foundinitiatornames) == 0):
msg = (_("Error finding %(name)s.")
% {'name': name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug("Found %(name)s: %(initiator)s.",
{'name': name,
'initiator': foundinitiatornames})
return foundinitiatornames
def _find_initiator_masking_group(
self, conn, controllerConfigService, initiatorNames):
"""Check to see if an initiatorGroup already exists.
NOTE: An initiator/wwn can only belong to one initiatorGroup.
If we were to attempt to create one with an initiator/wwn that is
already belong to another initiatorGroup, it would fail.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration service
:param initiatorNames: the list of initiator names
:returns: foundInitiatorMaskingGroup
"""
foundInitiatorMaskingGroupInstanceName = None
initiatorMaskingGroupInstanceNames = (
conn.AssociatorNames(controllerConfigService,
ResultClass='CIM_InitiatorMaskingGroup'))
for initiatorMaskingGroupInstanceName in (
initiatorMaskingGroupInstanceNames):
# Check that it hasn't been deleted. If it has, break out
# of the for loop.
instance = self.utils.get_existing_instance(
conn, initiatorMaskingGroupInstanceName)
if instance is None:
# MaskingGroup doesn't exist any more.
break
storageHardwareIdInstances = (
conn.Associators(initiatorMaskingGroupInstanceName,
ResultClass='EMC_StorageHardwareID'))
for storageHardwareIdInstance in storageHardwareIdInstances:
# If EMC_StorageHardwareID matches the initiator,
# we found the existing CIM_InitiatorMaskingGroup.
hardwareid = storageHardwareIdInstance['StorageID']
for initiator in initiatorNames:
if six.text_type(hardwareid).lower() == (
six.text_type(initiator).lower()):
foundInitiatorMaskingGroupInstanceName = (
initiatorMaskingGroupInstanceName)
break
if foundInitiatorMaskingGroupInstanceName is not None:
break
if foundInitiatorMaskingGroupInstanceName is not None:
break
return foundInitiatorMaskingGroupInstanceName
def _get_storage_hardware_id_instance_names(
self, conn, initiatorNames, storageSystemName):
"""Given a list of initiator names find CIM_StorageHardwareID instance.
:param conn: the connection to the ecom server
:param initiatorNames: the list of initiator names
:param storageSystemName: the storage system name
:returns: list -- foundHardwardIDsInstanceNames
"""
foundHardwardIDsInstanceNames = []
hardwareIdManagementService = (
self.utils.find_storage_hardwareid_service(
conn, storageSystemName))
hardwareIdInstances = (
self.utils.get_hardware_id_instances_from_array(
conn, hardwareIdManagementService))
for hardwareIdInstance in hardwareIdInstances:
storageId = hardwareIdInstance['StorageID']
for initiatorName in initiatorNames:
if storageId.lower() == initiatorName.lower():
# Check that the found hardwareId has been deleted.
# If it has, we don't want to add it to the list.
instance = self.utils.get_existing_instance(
conn, hardwareIdInstance.path)
if instance is None:
# HardwareId doesn't exist. Skip it.
break
foundHardwardIDsInstanceNames.append(
hardwareIdInstance.path)
break
LOG.debug(
"The found hardware IDs are : %(foundHardwardIDsInstanceNames)s.",
{'foundHardwardIDsInstanceNames': foundHardwardIDsInstanceNames})
return foundHardwardIDsInstanceNames
def _get_initiator_group_from_job(self, conn, job):
"""After creating an new initiator group find it and return it.
:param conn: the connection to the ecom server
:param job: the create initiator group job
:returns: dict -- initiatorDict
"""
associators = conn.Associators(
job['Job'],
ResultClass='CIM_InitiatorMaskingGroup')
volpath = associators[0].path
initiatorDict = {}
initiatorDict['classname'] = volpath.classname
keys = {}
keys['CreationClassName'] = volpath['CreationClassName']
keys['SystemName'] = volpath['SystemName']
keys['DeviceID'] = volpath['DeviceID']
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
initiatorDict['keybindings'] = keys
return initiatorDict
def _create_masking_view(
self, conn, configService, maskingViewName, deviceMaskingGroup,
targetMaskingGroup, initiatorMaskingGroup, extraSpecs):
"""After creating an new initiator group find it and return it.
:param conn: the connection to the ecom server
:param configService: the create initiator group job
:param maskingViewName: the masking view name string
:param deviceMaskingGroup: device(storage) masking group (instanceName)
:param targetMaskingGroup: target(port) masking group (instanceName)
:param initiatorMaskingGroup: initiator masking group (instanceName)
:param extraSpecs: extra specifications
:returns: int -- return code
:returns: dict -- job
:raises VolumeBackendAPIException:
"""
rc, job = conn.InvokeMethod(
'CreateMaskingView', configService, ElementName=maskingViewName,
InitiatorMaskingGroup=initiatorMaskingGroup,
DeviceMaskingGroup=deviceMaskingGroup,
TargetMaskingGroup=targetMaskingGroup)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Create Masking View: %(groupName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'groupName': maskingViewName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.info("Created new masking view : %(maskingViewName)s.",
{'maskingViewName': maskingViewName})
return rc, job
def find_new_masking_view(self, conn, jobDict):
"""Find the newly created volume.
:param conn: the connection to the ecom server
:param jobDict: the job dictionary
:returns: dict -- maskingViewInstance
"""
associators = conn.Associators(
jobDict['Job'],
ResultClass='Symm_LunMaskingView')
mvpath = associators[0].path
maskingViewInstance = {}
maskingViewInstance['classname'] = mvpath.classname
keys = {}
keys['CreationClassName'] = mvpath['CreationClassName']
keys['SystemName'] = mvpath['SystemName']
keys['DeviceID'] = mvpath['DeviceID']
keys['SystemCreationClassName'] = mvpath['SystemCreationClassName']
maskingViewInstance['keybindings'] = keys
return maskingViewInstance
def _get_storage_group_from_masking_view(
self, conn, maskingViewName, storageSystemName):
"""Gets the Device Masking Group from masking view.
:param conn: the connection to the ecom server
:param maskingViewName: the masking view name (String)
:param storageSystemName: storage system name (String)
:returns: instance name foundStorageGroupInstanceName
"""
foundStorageGroupInstanceName = None
foundView = self._find_masking_view(
conn, maskingViewName, storageSystemName)
if foundView is not None:
foundStorageGroupInstanceName = (
self._get_storage_group_from_masking_view_instance(
conn, foundView))
LOG.debug(
"Masking view: %(view)s DeviceMaskingGroup: %(masking)s.",
{'view': maskingViewName,
'masking': foundStorageGroupInstanceName})
else:
LOG.warning("Unable to find Masking view: %(view)s.",
{'view': maskingViewName})
return foundStorageGroupInstanceName
def _get_storage_group_from_masking_view_instance(
self, conn, maskingViewInstance):
"""Gets the Device Masking Group from masking view instance.
:param conn: the connection to the ecom server
:param maskingViewInstance: the masking view instance
:returns: instance name foundStorageGroupInstanceName
"""
foundStorageGroupInstanceName = None
groups = conn.AssociatorNames(
maskingViewInstance,
ResultClass='CIM_DeviceMaskingGroup')
if len(groups) > 0:
foundStorageGroupInstanceName = groups[0]
return foundStorageGroupInstanceName
def _get_storage_group_instance_name(
self, conn, maskingViewDict,
defaultStorageGroupInstanceName):
"""Gets the storage group instance name.
If fastPolicy name is None then NON FAST is assumed.
If it is a valid fastPolicy name then associate the new storage
group with the fast policy.
If we are using an existing storage group then we must check that
it is associated with the correct fast policy.
:param conn: the connection to the ecom server
:param maskingViewDict: the masking view dictionary
:param defaultStorageGroupInstanceName: default storage group instance
name (can be None for Non FAST)
:returns: instance name storageGroupInstanceName
:raises VolumeBackendAPIException:
"""
storageGroupInstanceName = self.utils.find_storage_masking_group(
conn, maskingViewDict['controllerConfigService'],
maskingViewDict['sgGroupName'])
if storageGroupInstanceName is None:
storageGroupInstanceName = self._create_storage_group(
conn, maskingViewDict,
defaultStorageGroupInstanceName)
if storageGroupInstanceName is None:
errorMessage = (_(
"Cannot create or find an storage group with name "
"%(sgGroupName)s.")
% {'sgGroupName': maskingViewDict['sgGroupName']})
LOG.error(errorMessage)
raise exception.VolumeBackendAPIException(data=errorMessage)
return storageGroupInstanceName
def _get_port_group_instance_name(
self, conn, controllerConfigService, pgGroupName):
"""Gets the port group instance name.
The portGroup name has been defined in the EMC Config file if it
does not exist the operation should fail.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration server
:param pgGroupName: the port group name
:returns: instance name foundPortGroupInstanceName
"""
foundPortGroupInstanceName = self.find_port_group(
conn, controllerConfigService, pgGroupName)
if foundPortGroupInstanceName is None:
LOG.error(
"Cannot find a portGroup with name %(pgGroupName)s. "
"The port group for a masking view must be pre-defined.",
{'pgGroupName': pgGroupName})
return foundPortGroupInstanceName
LOG.info(
"Port group instance name is %(foundPortGroupInstanceName)s.",
{'foundPortGroupInstanceName': foundPortGroupInstanceName})
return foundPortGroupInstanceName
def _get_initiator_group_instance_name(
self, conn, controllerConfigService, igGroupName, connector,
storageSystemName, extraSpecs):
"""Gets the initiator group instance name.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration server
:param igGroupName: the port group name
:param connector: the connector object
:param storageSystemName: the storage system name
:param extraSpecs: extra specifications
:returns: foundInitiatorGroupInstanceName
"""
foundInitiatorGroupInstanceName = (self._create_or_get_initiator_group(
conn, controllerConfigService, igGroupName, connector,
storageSystemName, extraSpecs))
if foundInitiatorGroupInstanceName is None:
LOG.error("Cannot create or find an initiator group with "
"name %(igGroupName)s.",
{'igGroupName': igGroupName})
return foundInitiatorGroupInstanceName
def _get_masking_view_instance_name(
self, conn, controllerConfigService, maskingViewName,
storageGroupInstanceName, portGroupInstanceName,
initiatorGroupInstanceName, extraSpecs):
"""Gets the masking view instance name.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration server
:param maskingViewName: the masking view name (String)
:param storageGroupInstanceName: the storage group instance name
:param portGroupInstanceName: the port group instance name
:param initiatorGroupInstanceName: the initiator group instance name
:param extraSpecs: extra specifications
:returns: instance name foundMaskingViewInstanceName
"""
_rc, job = (
self._create_masking_view(
conn, controllerConfigService, maskingViewName,
storageGroupInstanceName, portGroupInstanceName,
initiatorGroupInstanceName, extraSpecs))
foundMaskingViewInstanceName = self.find_new_masking_view(conn, job)
if foundMaskingViewInstanceName is None:
LOG.error(
"Cannot find the new masking view just created with name "
"%(maskingViewName)s.",
{'maskingViewName': maskingViewName})
return foundMaskingViewInstanceName
def _check_if_rollback_action_for_masking_required(
self, conn, rollbackDict):
"""This is a rollback action for FAST.
We need to be able to return the volume to the default storage group
if anything has gone wrong. The volume can also potentially belong to
a storage group that is not the default depending on where
the exception occurred. We also may need to clean up any unused
initiator groups.
:param conn: the connection to the ecom server
:param rollbackDict: the rollback dictionary
:returns: message
:raises VolumeBackendAPIException:
"""
message = None
# Check if ig has been created. If so, check for other
# masking views associated with the ig. If none, remove
# initiators and delete ig.
self._check_ig_rollback(
conn, rollbackDict['controllerConfigService'],
rollbackDict['igGroupName'], rollbackDict['connector'],
rollbackDict['extraSpecs'])
try:
foundStorageGroupInstanceName = (
self.utils.get_storage_group_from_volume(
conn, rollbackDict['volumeInstance'].path,
rollbackDict['sgGroupName']))
# Volume is not associated with any storage group so add
# it back to the default.
if not foundStorageGroupInstanceName:
if rollbackDict['isV3']:
errorMessage = self._check_adding_volume_to_storage_group(
conn, rollbackDict,
rollbackDict['defaultStorageGroupInstanceName'])
if errorMessage:
LOG.error(errorMessage)
message = (_("V3 rollback"))
else:
LOG.warning(
"No storage group found. "
"Performing rollback on Volume: %(volumeName)s "
"To return it to the default storage group for FAST "
"policy %(fastPolicyName)s.",
{'volumeName': rollbackDict['volumeName'],
'fastPolicyName': rollbackDict['fastPolicyName']})
assocDefaultStorageGroupName = (
self.fast
.add_volume_to_default_storage_group_for_fast_policy(
conn,
rollbackDict['controllerConfigService'],
rollbackDict['volumeInstance'],
rollbackDict['volumeName'],
rollbackDict['fastPolicyName'],
rollbackDict['extraSpecs']))
if assocDefaultStorageGroupName is None:
LOG.error(
"Failed to Roll back to re-add volume "
"%(volumeName)s "
"to default storage group for fast policy "
"%(fastPolicyName)s: Please contact your sys "
"admin to get the volume re-added manually.",
{'volumeName': rollbackDict['volumeName'],
'fastPolicyName': rollbackDict['fastPolicyName']})
message = (_("V2 rollback, volume is not in any storage "
"group."))
else:
LOG.info("The storage group found is "
"%(foundStorageGroupInstanceName)s.",
{'foundStorageGroupInstanceName':
foundStorageGroupInstanceName})
# Check the name, see if it is the default storage group
# or another.
if (foundStorageGroupInstanceName !=
rollbackDict['defaultStorageGroupInstanceName']):
# Remove it from its current masking view and return it
# to its default masking view if fast is enabled or slo
# is defined.
self.remove_and_reset_members(
conn,
rollbackDict['controllerConfigService'],
rollbackDict['volumeInstance'],
rollbackDict['volumeName'],
rollbackDict['extraSpecs'])
message = (_("Rollback - Volume in another storage "
"group besides default storage group."))
except Exception:
errorMessage = (_(
"Rollback for Volume: %(volumeName)s has failed. "
"Please contact your system administrator to manually return "
"your volume to the default storage group for fast policy/ "
"slo.")
% {'volumeName': rollbackDict['volumeName']})
LOG.exception(errorMessage)
raise exception.VolumeBackendAPIException(data=errorMessage)
return message
def _find_new_initiator_group(self, conn, maskingGroupDict):
"""After creating an new initiator group find it and return it.
:param conn: connection to the ecom server
:param maskingGroupDict: the maskingGroupDict dict
:returns: instance name foundInitiatorGroupInstanceName
"""
foundInitiatorGroupInstanceName = None
if 'MaskingGroup' in maskingGroupDict:
foundInitiatorGroupInstanceName = maskingGroupDict['MaskingGroup']
return foundInitiatorGroupInstanceName
def _get_initiator_group_from_masking_view(
self, conn, maskingViewName, storageSystemName):
"""Given the masking view name get the initiator group from it.
:param conn: connection to the ecom server
:param maskingViewName: the name of the masking view
:param storageSystemName: the storage system name
:returns: instance name foundInitiatorMaskingGroupInstanceName
"""
foundInitiatorMaskingGroupInstanceName = None
foundView = self._find_masking_view(
conn, maskingViewName, storageSystemName)
if foundView is not None:
groups = conn.AssociatorNames(
foundView,
ResultClass='CIM_InitiatorMaskingGroup')
if len(groups):
foundInitiatorMaskingGroupInstanceName = groups[0]
LOG.debug(
"Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.",
{'view': maskingViewName,
'masking': foundInitiatorMaskingGroupInstanceName})
else:
LOG.warning("Unable to find Masking view: %(view)s.",
{'view': maskingViewName})
return foundInitiatorMaskingGroupInstanceName
def _verify_initiator_group_from_masking_view(
self, conn, controllerConfigService, maskingViewName, connector,
storageSystemName, igGroupName, extraSpecs):
"""Check that the initiator group contains the correct initiators.
If using an existing masking view check that the initiator group
contains the correct initiators. If it does not contain the correct
initiators then we delete the initiator group from the masking view,
re-create it with the correct initiators and add it to the masking view
NOTE: EMC does not support ModifyMaskingView so we must first
delete the masking view and recreate it.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param maskingViewName: maskingview name (String)
:param connector: the connector dict
:param storageSystemName: the storage System Name (string)
:param igGroupName: the initiator group name (String)
:param extraSpecs: extra specifications
:returns: boolean
"""
initiatorNames = self._find_initiator_names(conn, connector)
foundInitiatorGroupFromConnector = self._find_initiator_masking_group(
conn, controllerConfigService, initiatorNames)
foundInitiatorGroupFromMaskingView = (
self._get_initiator_group_from_masking_view(
conn, maskingViewName, storageSystemName))
if (foundInitiatorGroupFromConnector !=
foundInitiatorGroupFromMaskingView):
if foundInitiatorGroupFromMaskingView is not None:
maskingViewInstanceName = self._find_masking_view(
conn, maskingViewName, storageSystemName)
storageGroupInstanceName = (
self._get_storage_group_from_masking_view(
conn, maskingViewName, storageSystemName))
portGroupInstanceName = self._get_port_group_from_masking_view(
conn, maskingViewName, storageSystemName)
if foundInitiatorGroupFromConnector is None:
storageHardwareIDInstanceNames = (
self._get_storage_hardware_id_instance_names(
conn, initiatorNames, storageSystemName))
if not storageHardwareIDInstanceNames:
LOG.info(
"Initiator Name(s) %(initiatorNames)s are not on "
"array %(storageSystemName)s.",
{'initiatorNames': initiatorNames,
'storageSystemName': storageSystemName})
storageHardwareIDInstanceNames = (
self._create_hardware_ids(conn, initiatorNames,
storageSystemName))
if not storageHardwareIDInstanceNames:
LOG.error(
"Failed to create hardware id(s) on "
"%(storageSystemName)s.",
{'storageSystemName': storageSystemName})
return False
igFromMaskingViewInstance = conn.GetInstance(
foundInitiatorGroupFromMaskingView, LocalOnly=False)
# if the current foundInitiatorGroupFromMaskingView name
# matches the igGroupName supplied for the new group, the
# existing ig needs to be deleted before the new one with
# the correct initiators can be created.
if (igFromMaskingViewInstance['ElementName'] ==
igGroupName):
# Masking view needs to be deleted before IG
# can be deleted.
self._delete_masking_view(
conn, controllerConfigService, maskingViewName,
maskingViewInstanceName, extraSpecs)
maskingViewInstanceName = None
self._delete_initiators_from_initiator_group(
conn, controllerConfigService,
foundInitiatorGroupFromMaskingView,
igGroupName)
self._delete_initiator_group(
conn, controllerConfigService,
foundInitiatorGroupFromMaskingView,
igGroupName, extraSpecs)
foundInitiatorGroupFromConnector = (
self._create_initiator_Group(
conn, controllerConfigService, igGroupName,
storageHardwareIDInstanceNames, extraSpecs))
if (foundInitiatorGroupFromConnector is not None and
storageGroupInstanceName is not None and
portGroupInstanceName is not None):
if maskingViewInstanceName:
# Existing masking view needs to be deleted before
# a new one can be created.
self._delete_masking_view(
conn, controllerConfigService, maskingViewName,
maskingViewInstanceName, extraSpecs)
newMaskingViewInstanceName = (
self._get_masking_view_instance_name(
conn, controllerConfigService, maskingViewName,
storageGroupInstanceName, portGroupInstanceName,
foundInitiatorGroupFromConnector, extraSpecs))
if newMaskingViewInstanceName is not None:
LOG.debug(
"The old masking view has been replaced: "
"%(maskingViewName)s.",
{'maskingViewName': maskingViewName})
else:
LOG.error(
"One of the components of the original masking view "
"%(maskingViewName)s cannot be retrieved so "
"please contact your system administrator to check "
"that the correct initiator(s) are part of masking.",
{'maskingViewName': maskingViewName})
return False
return True
def _create_initiator_Group(
self, conn, controllerConfigService, igGroupName,
hardwareIdinstanceNames, extraSpecs):
"""Create a new initiator group.
Given a list of hardwareId Instance name create a new
initiator group.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param igGroupName: the initiator group name (String)
:param hardwareIdinstanceNames: one or more hardware id instance names
:param extraSpecs: extra specifications
:returns: foundInitiatorGroupInstanceName
:raises VolumeBackendAPIException:
"""
rc, job = conn.InvokeMethod(
'CreateGroup', controllerConfigService, GroupName=igGroupName,
Type=self.utils.get_num(INITIATORGROUPTYPE, '16'),
Members=hardwareIdinstanceNames)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Create Group: %(groupName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'groupName': igGroupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
foundInitiatorGroupInstanceName = self._find_new_initiator_group(
conn, job)
return foundInitiatorGroupInstanceName
def _check_ig_rollback(
self, conn, controllerConfigService,
igGroupName, connector, extraSpecs):
"""Check if rollback action is required on an initiator group.
If anything goes wrong on a masking view creation, we need to check if
the process created a now-stale initiator group before failing, i.e.
an initiator group a) matching the name used in the mv process and
b) not associated with any other masking views.
If a stale ig exists, remove the initiators and delete the ig.
:param conn: the ecom connection
:param controllerConfigService: controller config service
:param igGroupName: the initiator group name
:param connector: the connector object
:param extraSpecs: extra specifications
"""
initiatorNames = self._find_initiator_names(conn, connector)
foundInitiatorGroupInstanceName = self._find_initiator_masking_group(
conn, controllerConfigService, initiatorNames)
if foundInitiatorGroupInstanceName:
initiatorGroupInstance = conn.GetInstance(
foundInitiatorGroupInstanceName, LocalOnly=False)
if initiatorGroupInstance['ElementName'] == igGroupName:
host = igGroupName.split("-")[1]
LOG.debug("Searching for masking views associated with "
"%(igGroupName)s",
{'igGroupName': igGroupName})
self._last_volume_delete_initiator_group(
conn, controllerConfigService,
foundInitiatorGroupInstanceName, extraSpecs, host)
def _get_port_group_from_masking_view(
self, conn, maskingViewName, storageSystemName):
"""Given the masking view name get the port group from it.
:param conn: connection to the ecom server
:param maskingViewName: the name of the masking view
:param storageSystemName: the storage system name
:returns: instance name foundPortMaskingGroupInstanceName
"""
foundPortMaskingGroupInstanceName = None
foundView = self._find_masking_view(
conn, maskingViewName, storageSystemName)
if foundView:
groups = conn.AssociatorNames(
foundView,
ResultClass='CIM_TargetMaskingGroup')
if len(groups) > 0:
foundPortMaskingGroupInstanceName = groups[0]
LOG.debug(
"Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.",
{'view': maskingViewName,
'masking': foundPortMaskingGroupInstanceName})
return foundPortMaskingGroupInstanceName
def _delete_masking_view(
self, conn, controllerConfigService, maskingViewName,
maskingViewInstanceName, extraSpecs):
"""Delete a masking view.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param maskingViewName: maskingview name (String)
:param maskingViewInstanceName: the masking view instance name
:param extraSpecs: extra specifications
:raises VolumeBackendAPIException:
"""
rc, job = conn.InvokeMethod('DeleteMaskingView',
controllerConfigService,
ProtocolController=maskingViewInstanceName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Modifying masking view : %(groupName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'groupName': maskingViewName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
def get_masking_view_from_storage_group(
self, conn, storageGroupInstanceName):
"""Get the associated maskingview instance name.
Given storage group instance name, get the associated masking
view instance name.
:param conn: connection to the ecom server
:param storageGroupInstanceName: the storage group instance name
:returns: instance name foundMaskingViewInstanceName
"""
foundMaskingViewInstanceName = None
maskingViews = conn.AssociatorNames(
storageGroupInstanceName,
ResultClass='Symm_LunMaskingView')
if len(maskingViews) > 0:
foundMaskingViewInstanceName = maskingViews[0]
return foundMaskingViewInstanceName
def add_volume_to_storage_group(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, sgGroupName, extraSpecs):
"""Add a volume to an existing storage group.
:param conn: connection to ecom server
:param controllerConfigService: the controller configuration service
:param storageGroupInstanceName: storage group instance name
:param volumeInstance: the volume instance
:param volumeName: the name of the volume (String)
:param sgGroupName: the name of the storage group (String)
:param extraSpecs: additional info
:returns: int -- rc the return code of the job
:returns: dict -- the job dict
"""
self.provision.add_members_to_masking_group(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstance.path, volumeName, extraSpecs)
LOG.info(
"Added volume: %(volumeName)s to existing storage group "
"%(sgGroupName)s.",
{'volumeName': volumeName,
'sgGroupName': sgGroupName})
def remove_device_from_default_storage_group(
self, conn, controllerConfigService, volumeInstanceName,
volumeName, fastPolicyName, extraSpecs):
"""Remove the volume from the default storage group.
Remove the volume from the default storage group for the FAST
policy and return the default storage group instance name.
: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)
:param extraSpecs: additional info
:returns: instance name defaultStorageGroupInstanceName
"""
failedRet = None
defaultStorageGroupInstanceName, defaultSgName = (
self.fast.get_and_verify_default_storage_group(
conn, controllerConfigService, volumeInstanceName,
volumeName, fastPolicyName))
if defaultStorageGroupInstanceName is None:
LOG.warning(
"Volume %(volumeName)s was not first part of the default "
"storage group for the FAST Policy.",
{'volumeName': volumeName})
return failedRet
@coordination.synchronized("emc-sg-{sgName}")
def do_remove_vol_from_sg(sgName):
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, defaultStorageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage "
"group for fast before removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'volumeName': volumeName})
self.provision.remove_device_from_storage_group(
conn, controllerConfigService,
defaultStorageGroupInstanceName, volumeInstanceName,
volumeName, extraSpecs)
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, defaultStorageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage "
"group %(sg)s after removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'sg': sgName, 'volumeName': volumeName})
do_remove_vol_from_sg(defaultStorageGroupInstanceName['ElementName'])
# Required for unit tests.
emptyStorageGroupInstanceName = (
self._wrap_get_storage_group_from_volume(conn, volumeInstanceName,
defaultSgName))
if emptyStorageGroupInstanceName is not None:
LOG.error(
"Failed to remove %(volumeName)s from the default storage "
"group for the FAST Policy.",
{'volumeName': volumeName})
return failedRet
return defaultStorageGroupInstanceName
def _wrap_get_storage_group_from_volume(self, conn, volumeInstanceName,
defaultSgName):
"""Wrapper for get_storage_group_from_volume.
Needed for override in tests.
:param conn: the connection to the ecom server
:param volumeInstanceName: the volume instance name
:param defaultSgName: the default storage group name
:returns: emptyStorageGroupInstanceName
"""
return self.utils.get_storage_group_from_volume(
conn, volumeInstanceName, defaultSgName)
def get_devices_from_storage_group(
self, conn, storageGroupInstanceName):
"""Get the associated volume Instance names.
Given the storage group instance name get the associated volume
Instance names.
:param conn: connection to the ecom server
:param storageGroupInstanceName: the storage group instance name
:returns: list -- volumeInstanceNames list of volume instance names
"""
volumeInstanceNames = conn.AssociatorNames(
storageGroupInstanceName,
ResultClass='EMC_StorageVolume')
return volumeInstanceNames
def get_associated_masking_groups_from_device(
self, conn, volumeInstanceName):
"""Get the associated storage groups from the volume Instance name.
Given the volume instance name get the associated storage group
instance names.
:param conn: connection to the ecom server
:param volumeInstanceName: the volume instance name
:returns: list -- list of storage group instance names
"""
maskingGroupInstanceNames = conn.AssociatorNames(
volumeInstanceName,
ResultClass='CIM_DeviceMaskingGroup',
AssocClass='CIM_OrderedMemberOfCollection')
if len(maskingGroupInstanceNames) > 0:
return maskingGroupInstanceNames
else:
LOG.info("Volume %(volumeName)s not in any storage group.",
{'volumeName': volumeInstanceName})
return None
def remove_and_reset_members(
self, conn, controllerConfigService, volumeInstance,
volumeName, extraSpecs, connector=None, reset=True):
"""This is called on a delete, unmap device or rollback.
If the connector is not None get the associated SG and remove volume
from the storage group, otherwise it is a VMAX3 deletion.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param volumeInstance: the volume Instance
:param volumeName: the volume name
:param extraSpecs: additional info
:param connector: optional
:param reset: reset, return to original SG (optional)
:returns: storageGroupInstanceName
"""
storageGroupInstanceName = None
if extraSpecs[ISV3]:
self._cleanup_deletion_v3(
conn, controllerConfigService, volumeInstance, extraSpecs)
else:
if connector:
storageGroupInstanceName = (
self._get_sg_associated_with_connector(
conn, controllerConfigService, volumeInstance.path,
volumeName, connector))
if storageGroupInstanceName:
self._remove_volume_from_sg(
conn, controllerConfigService,
storageGroupInstanceName,
volumeInstance, extraSpecs)
else:
LOG.warning("Cannot get storage from connector.")
if reset:
self._return_back_to_default_sg(
conn, controllerConfigService, volumeInstance, volumeName,
extraSpecs)
return storageGroupInstanceName
def _cleanup_deletion_v3(
self, conn, controllerConfigService, volumeInstance, extraSpecs):
"""Pre cleanup before VMAX3 deletion operation
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param volumeInstance: the volume instance
:param extraSpecs: the extra specifications
"""
storageGroupInstanceNames = (
self.get_associated_masking_groups_from_device(
conn, volumeInstance.path))
if storageGroupInstanceNames:
sgNum = len(storageGroupInstanceNames)
if len(storageGroupInstanceNames) > 1:
LOG.warning("Volume %(volumeName)s is belong to %(sgNum)s "
"storage groups.",
{'volumeName': volumeInstance['ElementName'],
'sgNum': sgNum})
for storageGroupInstanceName in storageGroupInstanceNames:
self._remove_volume_from_sg(
conn, controllerConfigService,
storageGroupInstanceName,
volumeInstance,
extraSpecs)
def _remove_volume_from_sg(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, extraSpecs):
"""Remove volume from storage group
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param storageGroupInstanceName: the SG instance name
:param volumeInstance: the volume instance
:param extraSpecs: the extra specifications
"""
instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False)
storageGroupName = instance['ElementName']
mvInstanceName = self.get_masking_view_from_storage_group(
conn, storageGroupInstanceName)
if mvInstanceName is None:
LOG.debug("Unable to get masking view %(maskingView)s "
"from storage group.",
{'maskingView': mvInstanceName})
@coordination.synchronized("emc-sg-{storageGroup}")
def do_remove_volume_from_sg(storageGroup):
volumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
numVolInStorageGroup = len(volumeInstanceNames)
LOG.debug(
"There are %(numVol)d volumes in the storage group "
"%(maskingGroup)s.",
{'numVol': numVolInStorageGroup,
'maskingGroup': storageGroup})
if numVolInStorageGroup == 1:
# Last volume in the storage group.
self._last_vol_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstance,
volumeInstance['ElementName'], extraSpecs)
else:
# Not the last volume so remove it from storage group
self._multiple_vols_in_SG(
conn, controllerConfigService,
storageGroupInstanceName, volumeInstance,
volumeInstance['ElementName'],
numVolInStorageGroup, extraSpecs)
return do_remove_volume_from_sg(storageGroupName)
else:
# need to lock masking view when we are locking the storage
# group to avoid possible deadlock situations from concurrent
# processes
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
@coordination.synchronized("emc-mv-{maskingView}")
@coordination.synchronized("emc-sg-{storageGroup}")
def do_remove_volume_from_sg(maskingView, storageGroup):
volumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
numVolInStorageGroup = len(volumeInstanceNames)
LOG.debug(
"There are %(numVol)d volumes in the storage group "
"%(maskingGroup)s associated with %(mvName)s",
{'numVol': numVolInStorageGroup,
'maskingGroup': storageGroup,
'mvName': maskingView})
if numVolInStorageGroup == 1:
# Last volume in the storage group.
self._last_vol_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstance,
volumeInstance['ElementName'], extraSpecs)
else:
# Not the last volume so remove it from storage group
self._multiple_vols_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
volumeInstance, volumeInstance['ElementName'],
numVolInStorageGroup, extraSpecs)
return do_remove_volume_from_sg(maskingViewName, storageGroupName)
def _last_vol_in_SG(
self, conn, controllerConfigService, storageGroupInstanceName,
storageGroupName, volumeInstance, volumeName, extraSpecs):
"""Steps if the volume is the last in a storage group.
1. Check if the volume is in a masking view.
2. If it is in a masking view, delete the masking view, remove the
initiators from the initiator group and delete the initiator
group if there are no other masking views associated with the
initiator group, remove the volume from the storage group, and
delete the storage group.
3. If it is not in a masking view, remove the volume from the
storage group and delete the storage group.
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param storageGroupInstanceName: the SG instance name
:param storageGroupName: the Storage group name (String)
:param volumeInstance: the volume instance
:param volumeName: the volume name
:param extraSpecs: the extra specifications
"""
status = False
LOG.debug("Only one volume remains in storage group "
"%(sgname)s. Driver will attempt cleanup.",
{'sgname': storageGroupName})
mvInstanceName = self.get_masking_view_from_storage_group(
conn, storageGroupInstanceName)
if mvInstanceName is None:
# Remove the volume from the storage group and delete the SG.
self._remove_last_vol_and_delete_sg(
conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstance.path,
volumeName, extraSpecs)
status = True
else:
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
def do_delete_mv_ig_and_sg():
return self._delete_mv_ig_and_sg(
conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName,
storageGroupName, volumeInstance, volumeName,
extraSpecs)
do_delete_mv_ig_and_sg()
status = True
return status
def _multiple_vols_in_SG(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, numVolsInSG, extraSpecs):
"""If the volume is not the last in the storage group
Remove the volume from the SG.
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param storageGroupInstanceName: the SG instance name
:param volumeInstance: the volume instance
:param volumeName: the volume name
:param numVolsInSG: the number of volumes in the SG
:param extraSpecs: the extra specifications
"""
LOG.debug("Start: number of volumes in masking storage group: "
"%(numVol)d", {'numVol': numVolsInSG})
self.provision.remove_device_from_storage_group(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstance.path, volumeName, extraSpecs)
LOG.debug(
"RemoveMembers for volume %(volumeName)s completed "
"successfully.", {'volumeName': volumeName})
volumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
LOG.debug(
"End: number of volumes in masking storage group: %(numVol)d.",
{'numVol': len(volumeInstanceNames)})
def _delete_mv_ig_and_sg(
self, conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName, storageGroupName,
volumeInstance, volumeName, extraSpecs):
"""Delete the Masking view, the storage Group and the initiator group.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param mvInstanceName: masking view instance name
:param maskingViewName: masking view name
:param storageGroupInstanceName: storage group instance name
:param maskingViewName: masking view name
:param volumeInstance: the volume Instance
:param volumeName: the volume name
:param extraSpecs: extra specs
"""
isV3 = extraSpecs[ISV3]
fastPolicyName = extraSpecs.get(FASTPOLICY, None)
host = maskingViewName.split("-")[1]
storageSystemInstanceName = self.utils.find_storage_system(
conn, controllerConfigService)
initiatorGroupInstanceName = (
self.get_initiator_group_from_masking_view(conn, mvInstanceName))
self._last_volume_delete_masking_view(
conn, controllerConfigService, mvInstanceName,
maskingViewName, extraSpecs)
self._last_volume_delete_initiator_group(
conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, host)
if not isV3:
isTieringPolicySupported, tierPolicyServiceInstanceName = (
self._get_tiering_info(conn, storageSystemInstanceName,
fastPolicyName))
self._get_and_remove_rule_association(
conn, fastPolicyName,
isTieringPolicySupported,
tierPolicyServiceInstanceName,
storageSystemInstanceName['Name'],
storageGroupInstanceName, extraSpecs)
self._remove_last_vol_and_delete_sg(
conn, controllerConfigService, storageGroupInstanceName,
storageGroupName, volumeInstance.path, volumeName,
extraSpecs)
LOG.debug(
"Volume %(volumeName)s successfully removed from SG and "
"Storage Group %(storageGroupName)s successfully deleted. ",
{'volumeName': volumeName,
'storageGroupName': storageGroupName})
def _return_back_to_default_sg(
self, conn, controllerConfigService, volumeInstance, volumeName,
extraSpecs):
"""Return volume to default storage group
Moving the volume to the default SG for VMAX3 and
FAST for VMAX2.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param volumeInstance: the volume Instance
:param volumeName: the volume name
:param extraSpecs: extra specs
"""
# Add it back to the default storage group.
if extraSpecs[ISV3]:
self.return_volume_to_default_storage_group_v3(
conn, controllerConfigService,
volumeInstance, volumeName, extraSpecs)
else:
# V2 if FAST POLICY enabled, move the volume to the default
# SG.
fastPolicyName = extraSpecs.get(FASTPOLICY, None)
storageSystemInstanceName = self.utils.find_storage_system(
conn, controllerConfigService)
isTieringPolicySupported, __ = (
self._get_tiering_info(conn, storageSystemInstanceName,
fastPolicyName))
if fastPolicyName is not None and isTieringPolicySupported:
self._cleanup_tiering(
conn, controllerConfigService, fastPolicyName,
volumeInstance, volumeName, extraSpecs)
def _get_sg_associated_with_connector(
self, conn, controllerConfigService, volumeInstanceName,
volumeName, connector):
"""Get storage group associated with connector.
If the connector gets passed then extra logic required to
get storage group.
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param volumeInstanceName: the volume instance name
:param volumeName: the volume name (String)
:param connector: the connector object
:returns: storageGroupInstanceName(can be None)
"""
return self._get_sg_or_mv_associated_with_initiator(
conn, controllerConfigService, volumeInstanceName,
volumeName, connector, True)
def _get_tiering_info(
self, conn, storageSystemInstanceName, fastPolicyName):
"""Get tiering specifics.
:param conn: the ecom connection
:param storageSystemInstanceName: storage system instance name
:param fastPolicyName:
:returns: boolean -- isTieringPolicySupported
:returns: tierPolicyServiceInstanceName
"""
isTieringPolicySupported = False
tierPolicyServiceInstanceName = None
if fastPolicyName is not None:
tierPolicyServiceInstanceName = self.utils.get_tier_policy_service(
conn, storageSystemInstanceName)
isTieringPolicySupported = self.fast.is_tiering_policy_enabled(
conn, tierPolicyServiceInstanceName)
LOG.debug(
"FAST policy enabled on %(storageSystem)s: %(isSupported)s",
{'storageSystem': storageSystemInstanceName,
'isSupported': isTieringPolicySupported})
return isTieringPolicySupported, tierPolicyServiceInstanceName
def _last_volume_delete_masking_view(
self, conn, controllerConfigService, mvInstanceName,
maskingViewName, extraSpecs):
"""Delete the masking view.
Delete the masking view if the volume is the last one in the
storage group.
:param conn: the ecom connection
:param controllerConfigService: controller config service
:param mvInstanceName: masking view instance name
:param maskingViewName: masking view name
:param extraSpecs: extra specifications
"""
LOG.debug(
"Last volume in the storage group, deleting masking view "
"%(maskingViewName)s.",
{'maskingViewName': maskingViewName})
self._delete_masking_view(
conn, controllerConfigService, maskingViewName,
mvInstanceName, extraSpecs)
mvInstance = self.utils.get_existing_instance(
conn, mvInstanceName)
if mvInstance:
exceptionMessage = (_(
"Masking view %(maskingViewName)s "
"was not deleted successfully") %
{'maskingViewName': maskingViewName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
else:
LOG.info(
"Masking view %(maskingViewName)s successfully deleted.",
{'maskingViewName': maskingViewName})
def _get_and_remove_rule_association(
self, conn, fastPolicyName, isTieringPolicySupported,
tierPolicyServiceInstanceName, storageSystemName,
storageGroupInstanceName, extraSpecs):
"""Remove the storage group from the policy rule.
:param conn: the ecom connection
:param fastPolicyName: the fast policy name
:param isTieringPolicySupported: boolean
:param tierPolicyServiceInstanceName: the tier policy instance name
:param storageSystemName: storage system name
:param storageGroupInstanceName: the storage group instance name
:param extraSpecs: additional info
"""
# Disassociate storage group from FAST policy.
if fastPolicyName is not None and isTieringPolicySupported is True:
tierPolicyInstanceName = self.fast.get_tier_policy_by_name(
conn, storageSystemName, fastPolicyName)
LOG.debug(
"Policy: %(policy)s, policy service:%(service)s, "
"masking group: %(maskingGroup)s.",
{'policy': tierPolicyInstanceName,
'service': tierPolicyServiceInstanceName,
'maskingGroup': storageGroupInstanceName})
self.fast.delete_storage_group_from_tier_policy_rule(
conn, tierPolicyServiceInstanceName,
storageGroupInstanceName, tierPolicyInstanceName, extraSpecs)
def return_volume_to_default_storage_group_v3(
self, conn, controllerConfigurationService,
volumeInstance, volumeName, extraSpecs):
"""Return volume to the default storage group in v3.
:param conn: the ecom connection
:param controllerConfigurationService: controller config service
:param volumeInstance: volumeInstance
:param volumeName: the volume name
:param extraSpecs: additional info
:raises VolumeBackendAPIException:
"""
rep_enabled = self.utils.is_replication_enabled(extraSpecs)
isCompressionDisabled = self.utils.is_compression_disabled(extraSpecs)
storageGroupName = self.utils.get_v3_storage_group_name(
extraSpecs[self.utils.POOL], extraSpecs[self.utils.SLO],
extraSpecs[self.utils.WORKLOAD], isCompressionDisabled,
rep_enabled)
storageGroupInstanceName = self.utils.find_storage_masking_group(
conn, controllerConfigurationService, storageGroupName)
if not storageGroupInstanceName:
storageGroupInstanceName = (
self.provisionv3.create_storage_group_v3(
conn, controllerConfigurationService, storageGroupName,
extraSpecs[self.utils.POOL], extraSpecs[self.utils.SLO],
extraSpecs[self.utils.WORKLOAD], extraSpecs,
isCompressionDisabled))
if not storageGroupInstanceName:
errorMessage = (_("Failed to create storage group "
"%(storageGroupName)s.") %
{'storageGroupName': storageGroupName})
LOG.error(errorMessage)
raise exception.VolumeBackendAPIException(data=errorMessage)
self._add_volume_to_sg_and_verify(
conn, controllerConfigurationService,
storageGroupInstanceName, volumeInstance, volumeName,
storageGroupName, extraSpecs)
def _cleanup_tiering(
self, conn, controllerConfigService, fastPolicyName,
volumeInstance, volumeName, extraSpecs):
"""Clean up tiering.
:param conn: the ecom connection
:param controllerConfigService: the controller configuration service
:param fastPolicyName: the fast policy name
:param volumeInstance: volume instance
:param volumeName: the volume name
:param extraSpecs: additional info
"""
defaultStorageGroupInstanceName = (
self.fast.get_policy_default_storage_group(
conn, controllerConfigService, fastPolicyName))
volumeInstanceNames = self.get_devices_from_storage_group(
conn, defaultStorageGroupInstanceName)
LOG.debug(
"Start: number of volumes in default storage group: %(numVol)d.",
{'numVol': len(volumeInstanceNames)})
defaultStorageGroupInstanceName = (
self.fast.add_volume_to_default_storage_group_for_fast_policy(
conn, controllerConfigService, volumeInstance, volumeName,
fastPolicyName, extraSpecs))
# Check default storage group number of volumes.
volumeInstanceNames = self.get_devices_from_storage_group(
conn, defaultStorageGroupInstanceName)
LOG.debug(
"End: number of volumes in default storage group: %(numVol)d.",
{'numVol': len(volumeInstanceNames)})
def get_target_wwns(self, conn, mvInstanceName):
"""Get the DA ports wwns.
:param conn: the ecom connection
:param mvInstanceName: masking view instance name
:returns: list -- the list of target wwns for the masking view
"""
targetWwns = []
targetPortInstanceNames = conn.AssociatorNames(
mvInstanceName,
ResultClass='Symm_FCSCSIProtocolEndpoint')
numberOfPorts = len(targetPortInstanceNames)
if numberOfPorts <= 0:
LOG.warning("No target ports found in "
"masking view %(maskingView)s.",
{'numPorts': len(targetPortInstanceNames),
'maskingView': mvInstanceName})
for targetPortInstanceName in targetPortInstanceNames:
targetWwns.append(targetPortInstanceName['Name'])
return targetWwns
def get_masking_view_by_volume(self, conn, volumeInstance, connector):
"""Given volume, retrieve the masking view instance name.
:param conn: the ecom connection
:param volumeInstance: the volume instance
:param connector: the connector object
:returns: masking view instance name
"""
storageSystemName = volumeInstance['SystemName']
controllerConfigService = (
self.utils.find_controller_configuration_service(
conn, storageSystemName))
volumeName = volumeInstance['ElementName']
mvInstanceName = (
self._get_sg_or_mv_associated_with_initiator(
conn, controllerConfigService, volumeInstance.path,
volumeName, connector, False))
return mvInstanceName
def get_masking_views_by_port_group(self, conn, portGroupInstanceName):
"""Given port group, retrieve the masking view instance name.
:param conn: the ecom connection
:param portGroupInstanceName: the instance name of the port group
:returns: masking view instance names
"""
mvInstanceNames = conn.AssociatorNames(
portGroupInstanceName, ResultClass='Symm_LunMaskingView')
return mvInstanceNames
def get_masking_views_by_initiator_group(
self, conn, initiatorGroupInstanceName):
"""Given initiator group, retrieve the masking view instance name.
Retrieve the list of masking view instances associated with the
initiator group instance name.
:param conn: the ecom connection
:param initiatorGroupInstanceName: the instance name of the
initiator group
:returns: list of masking view instance names
"""
mvInstanceNames = conn.AssociatorNames(
initiatorGroupInstanceName, ResultClass='Symm_LunMaskingView')
return mvInstanceNames
def get_port_group_from_masking_view(self, conn, maskingViewInstanceName):
"""Get the port group in a masking view.
:param conn: the ecom connection
:param maskingViewInstanceName: masking view instance name
:returns: portGroupInstanceName
"""
portGroupInstanceNames = conn.AssociatorNames(
maskingViewInstanceName, ResultClass='SE_TargetMaskingGroup')
if len(portGroupInstanceNames) > 0:
LOG.debug("Found port group %(pg)s in masking view %(mv)s.",
{'pg': portGroupInstanceNames[0],
'mv': maskingViewInstanceName})
return portGroupInstanceNames[0]
else:
LOG.warning("No port group found in masking view %(mv)s.",
{'mv': maskingViewInstanceName})
def get_initiator_group_from_masking_view(
self, conn, maskingViewInstanceName):
"""Get initiator group in a masking view.
:param conn: the ecom connection
:param maskingViewInstanceName: masking view instance name
:returns: initiatorGroupInstanceName or None if it is not found
"""
initiatorGroupInstanceNames = conn.AssociatorNames(
maskingViewInstanceName, ResultClass='SE_InitiatorMaskingGroup')
if len(initiatorGroupInstanceNames) > 0:
LOG.debug("Found initiator group %(ig)s in masking view %(mv)s.",
{'ig': initiatorGroupInstanceNames[0],
'mv': maskingViewInstanceName})
return initiatorGroupInstanceNames[0]
else:
LOG.warning("No Initiator group found in masking view "
"%(mv)s.", {'mv': maskingViewInstanceName})
def _get_sg_or_mv_associated_with_initiator(
self, conn, controllerConfigService, volumeInstanceName,
volumeName, connector, getSG=True):
"""Get storage group or masking view associated with connector.
If the connector gets passed then extra logic required to
get storage group.
:param conn: the ecom connection
:param controllerConfigService: storage system instance name
:param volumeInstanceName: volume instance name
:param volumeName: volume element name
:param connector: the connector object
:param getSG: True if to get storage group; otherwise get masking
:returns: foundInstanceName(can be None)
"""
foundInstanceName = None
initiatorNames = self._find_initiator_names(conn, connector)
igInstanceNameFromConnector = self._find_initiator_masking_group(
conn, controllerConfigService, initiatorNames)
# Device can be shared by multi-SGs in a multi-host attach case.
storageGroupInstanceNames = (
self.get_associated_masking_groups_from_device(
conn, volumeInstanceName))
LOG.debug("Found storage groups volume "
"%(volumeName)s is in: %(storageGroups)s",
{'volumeName': volumeName,
'storageGroups': storageGroupInstanceNames})
if storageGroupInstanceNames: # not empty
# Get the SG by IGs.
for sgInstanceName in storageGroupInstanceNames:
# Get maskingview from storage group.
mvInstanceName = self.get_masking_view_from_storage_group(
conn, sgInstanceName)
# Get initiator group from masking view.
if mvInstanceName:
LOG.debug("Found masking view associated with SG "
"%(storageGroup)s: %(maskingview)s",
{'maskingview': mvInstanceName,
'storageGroup': sgInstanceName})
igInstanceName = (
self.get_initiator_group_from_masking_view(
conn, mvInstanceName))
LOG.debug("Initiator Group in masking view %(ig)s: "
"IG associated with connector "
"%(igFromConnector)s.",
{'ig': igInstanceName,
'igFromConnector': igInstanceNameFromConnector})
if igInstanceName == igInstanceNameFromConnector:
if getSG is True:
foundInstanceName = sgInstanceName
LOG.debug("Found the storage group associated "
"with initiator %(initiator)s: "
"%(storageGroup)s",
{'initiator': initiatorNames,
'storageGroup': foundInstanceName})
else:
foundInstanceName = mvInstanceName
LOG.debug("Found the masking view associated with "
"initiator %(initiator)s: "
"%(maskingview)s.",
{'initiator': initiatorNames,
'maskingview': foundInstanceName})
break
return foundInstanceName
def _remove_last_vol_and_delete_sg(self, conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstanceName,
volumeName, extraSpecs):
"""Remove the last volume and delete the storage group
:param conn: the ecom connection
:param controllerConfigService: controller config service
:param storageGroupInstanceName: storage group instance name
:param storageGroupName: storage group name
:param volumeInstanceName: volume instance name
:param volumeName: volume name
:param extrSpecs: additional info
"""
self.provision.remove_device_from_storage_group(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstanceName, volumeName, extraSpecs)
LOG.debug(
"Remove the last volume %(volumeName)s completed "
"successfully.",
{'volumeName': volumeName})
# Delete storage group.
self.delete_storage_group(conn, controllerConfigService,
storageGroupInstanceName, extraSpecs)
def delete_storage_group(self, conn, controllerConfigService,
storageGroupInstanceName, extraSpecs):
"""Delete a given storage group.
:param conn: connection to the ecom server
:param controllerConfigService: controller config service
:param storageGroupInstanceName: the storage group instance
:param extraSpecs: the extra specifications
"""
# Delete storage group.
self._delete_storage_group(conn, controllerConfigService,
storageGroupInstanceName, extraSpecs)
storageGroupInstance = self.utils.get_existing_instance(
conn, storageGroupInstanceName)
if storageGroupInstance:
exceptionMessage = (
_("Storage group %(storageGroupName)s "
"was not deleted successfully")
% {'storageGroupName': storageGroupInstanceName})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
else:
LOG.debug("Storage Group successfully deleted.")
def _delete_storage_group(self, conn, controllerConfigService,
storageGroupInstanceName, extraSpecs):
"""Delete empty storage group
:param conn: the ecom connection
:param controllerConfigService: controller config service
:param storageGroupInstanceName: storage group instance name
:param extraSpecs: extra specifications
"""
rc, job = conn.InvokeMethod(
'DeleteGroup',
controllerConfigService,
MaskingGroup=storageGroupInstanceName,
Force=True)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Deleting Group: %(storageGroupName)s. "
"Return code: %(rc)lu. Error: %(error)s")
% {'storageGroupName': storageGroupInstanceName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
def _delete_initiator_group(self, conn, controllerConfigService,
initiatorGroupInstanceName, initiatorGroupName,
extraSpecs):
"""Delete an initiatorGroup.
:param conn - connection to the ecom server
:param controllerConfigService - controller config service
:param initiatorGroupInstanceName - the initiator group instance name
:param initiatorGroupName - initiator group name
:param extraSpecs: extra specifications
"""
rc, job = conn.InvokeMethod(
'DeleteGroup',
controllerConfigService,
MaskingGroup=initiatorGroupInstanceName,
Force=True)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Deleting Initiator Group: %(initiatorGroupName)s. "
"Return code: %(rc)lu. Error: %(error)s")
% {'initiatorGroupName': initiatorGroupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
else:
LOG.debug("Initiator group %(initiatorGroupName)s "
"is successfully deleted.",
{'initiatorGroupName': initiatorGroupName})
else:
LOG.debug("Initiator group %(initiatorGroupName)s "
"is successfully deleted.",
{'initiatorGroupName': initiatorGroupName})
def _delete_storage_hardware_id(self,
conn,
hardwareIdManagementService,
hardwareIdPath):
"""Delete given initiator path
Delete the initiator. Do not rise exception or failure if deletion
fails due to any reasons.
:param conn - connection to the ecom server
:param hardwareIdManagementService - hardware id management service
:param hardwareIdPath - The path of the initiator object
"""
ret = conn.InvokeMethod('DeleteStorageHardwareID',
hardwareIdManagementService,
HardwareID = hardwareIdPath)
if ret == 0:
LOG.debug("Deletion of initiator path %(hardwareIdPath)s "
"is successful.", {'hardwareIdPath': hardwareIdPath})
else:
LOG.warning("Deletion of initiator path %(hardwareIdPath)s "
"is failed.", {'hardwareIdPath': hardwareIdPath})
def _delete_initiators_from_initiator_group(self, conn,
controllerConfigService,
initiatorGroupInstanceName,
initiatorGroupName):
"""Delete initiators
Delete all initiators associated with the initiator group instance.
Cleanup whatever is possible. It will not return any failure or
rise exception if deletion fails due to any reasons.
:param conn - connection to the ecom server
:param controllerConfigService - controller config service
:param initiatorGroupInstanceName - the initiator group instance name
"""
storageHardwareIdInstanceNames = (
conn.AssociatorNames(initiatorGroupInstanceName,
ResultClass='SE_StorageHardwareID'))
if len(storageHardwareIdInstanceNames) == 0:
LOG.debug("No initiators found in Initiator group "
"%(initiatorGroupName)s.",
{'initiatorGroupName': initiatorGroupName})
return
storageSystemName = controllerConfigService['SystemName']
hardwareIdManagementService = (
self.utils.find_storage_hardwareid_service(conn,
storageSystemName))
for storageHardwareIdInstanceName in storageHardwareIdInstanceNames:
initiatorName = storageHardwareIdInstanceName['InstanceID']
hardwareIdPath = storageHardwareIdInstanceName
LOG.debug("Initiator %(initiatorName)s "
"will be deleted from the Initiator group "
"%(initiatorGroupName)s. HardwareIdPath is "
"%(hardwareIdPath)s.",
{'initiatorName': initiatorName,
'initiatorGroupName': initiatorGroupName,
'hardwareIdPath': hardwareIdPath})
self._delete_storage_hardware_id(conn,
hardwareIdManagementService,
hardwareIdPath)
def _last_volume_delete_initiator_group(
self, conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, host=None):
"""Delete the initiator group.
Delete the Initiator group if it has been created by the VMAX driver,
and if there are no masking views associated with it.
:param conn: the ecom connection
:param controllerConfigService: controller config service
:param igInstanceNames: initiator group instance name
:param extraSpecs: extra specifications
:param host: the short name of the host
"""
defaultInitiatorGroupName = None
initiatorGroupInstance = conn.GetInstance(initiatorGroupInstanceName)
initiatorGroupName = initiatorGroupInstance['ElementName']
protocol = self.utils.get_short_protocol_type(self.protocol)
if host:
defaultInitiatorGroupName = ((
"OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': host,
'protocol': protocol}))
if initiatorGroupName == defaultInitiatorGroupName:
maskingViewInstanceNames = (
self.get_masking_views_by_initiator_group(
conn, initiatorGroupInstanceName))
if len(maskingViewInstanceNames) == 0:
LOG.debug(
"Last volume associated with the initiator group - "
"deleting the associated initiator group "
"%(initiatorGroupName)s.",
{'initiatorGroupName': initiatorGroupName})
self._delete_initiators_from_initiator_group(
conn, controllerConfigService, initiatorGroupInstanceName,
initiatorGroupName)
self._delete_initiator_group(conn, controllerConfigService,
initiatorGroupInstanceName,
initiatorGroupName, extraSpecs)
else:
LOG.warning("Initiator group %(initiatorGroupName)s is "
"associated with masking views and can't be "
"deleted. Number of associated masking view "
"is: %(nmv)d.",
{'initiatorGroupName': initiatorGroupName,
'nmv': len(maskingViewInstanceNames)})
else:
LOG.warning("Initiator group %(initiatorGroupName)s was "
"not created by the VMAX driver so will "
"not be deleted by the VMAX driver.",
{'initiatorGroupName': initiatorGroupName})
def _create_hardware_ids(
self, conn, initiatorNames, storageSystemName):
"""Create hardwareIds for initiator(s).
:param conn: the connection to the ecom server
:param initiatorNames: the list of initiator names
:param storageSystemName: the storage system name
:returns: list -- foundHardwareIDsInstanceNames
"""
foundHardwareIDsInstanceNames = []
hardwareIdManagementService = (
self.utils.find_storage_hardwareid_service(
conn, storageSystemName))
for initiatorName in initiatorNames:
hardwareIdInstanceName = (
self.utils.create_storage_hardwareId_instance_name(
conn, hardwareIdManagementService, initiatorName))
LOG.debug(
"Created hardwareId Instance: %(hardwareIdInstanceName)s.",
{'hardwareIdInstanceName': hardwareIdInstanceName})
foundHardwareIDsInstanceNames.append(hardwareIdInstanceName)
return foundHardwareIDsInstanceNames
def _get_port_group_name_from_mv(self, conn, maskingViewName,
storageSystemName):
"""Get the port group name from the masking view.
:param conn: the connection to the ecom server
:param maskingViewName: the masking view name
:param storageSystemName: the storage system name
:returns: String - port group name
String - error message
"""
errorMessage = None
portGroupName = None
portGroupInstanceName = (
self._get_port_group_from_masking_view(
conn, maskingViewName, storageSystemName))
if portGroupInstanceName is None:
LOG.error(
"Cannot get port group from masking view: "
"%(maskingViewName)s.",
{'maskingViewName': maskingViewName})
else:
try:
portGroupInstance = (
conn.GetInstance(portGroupInstanceName))
portGroupName = (
portGroupInstance['ElementName'])
except Exception:
LOG.error(
"Cannot get port group name.")
return portGroupName, errorMessage
@coordination.synchronized('emc-sg-'
'{storageGroupName}')
def remove_device_from_storage_group(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, storageGroupName, extraSpecs):
"""Remove a device from a storage group.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller config service
:param storageGroupInstanceName: the sg instance
:param volumeInstance: the volume instance
:param extraSpecs: the extra specifications
"""
return self.provision.remove_device_from_storage_group(
conn, controllerConfigService, storageGroupInstanceName,
volumeInstance, volumeName, extraSpecs)