deb-cinder/cinder/volume/drivers/emc/emc_vmax_provision.py
Helen Walsh 035d6d3a30 VMAX driver - widen locking around storage groups
There is an issue around the locking of storage groups.
There is insufficent locking when multiple processes may
be adding and removing volumes from the same storage group
e.g. deleting the last replicated volume while another
process is creating one. This patch rectifies this issue.

Change-Id: I0138ace1e3d8f1e62d5422864481221915907a25
Closes-Bug: #1660374
(cherry picked from commit 84d463380a)
2017-02-09 16:30:13 +00:00

1080 lines
44 KiB
Python

# Copyright (c) 2012 - 2015 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
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.emc import emc_vmax_utils
LOG = logging.getLogger(__name__)
STORAGEGROUPTYPE = 4
POSTGROUPTYPE = 3
EMC_ROOT = 'root/emc'
THINPROVISIONINGCOMPOSITE = 32768
THINPROVISIONING = 5
SYNC_CLONE_LOCAL = 10
COPY_ON_WRITE = 6
TF_CLONE = 8
class EMCVMAXProvision(object):
"""Provisioning Class for SMI-S based EMC volume drivers.
This Provisioning class is for EMC volume drivers based on SMI-S.
It supports VMAX arrays.
"""
def __init__(self, prtcl):
self.protocol = prtcl
self.utils = emc_vmax_utils.EMCVMAXUtils(prtcl)
def delete_volume_from_pool(
self, conn, storageConfigservice, volumeInstanceName, volumeName,
extraSpecs):
"""Given the volume instance remove it from the pool.
:param conn: connection to the ecom server
:param storageConfigservice: volume created from job
:param volumeInstanceName: the volume instance name
:param volumeName: the volume name (String)
:param extraSpecs: additional info
:returns: int -- return code
:raises: VolumeBackendAPIException
"""
startTime = time.time()
if isinstance(volumeInstanceName, list):
theElements = volumeInstanceName
volumeName = 'Bulk Delete'
else:
theElements = [volumeInstanceName]
rc, job = conn.InvokeMethod(
'ReturnElementsToStoragePool', storageConfigservice,
TheElements=theElements)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Delete Volume: %(volumeName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'volumeName': volumeName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod EMCReturnToStoragePool took: "
"%(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc
def create_volume_from_pool(
self, conn, storageConfigService, volumeName,
poolInstanceName, volumeSize, extraSpecs):
"""Create the volume in the specified pool.
:param conn: the connection information to the ecom server
:param storageConfigService: the storage configuration service
:param volumeName: the volume name (String)
:param poolInstanceName: the pool instance name to create
the dummy volume in
:param volumeSize: volume size (String)
:param extraSpecs: additional info
:returns: dict -- the volume dict
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'CreateOrModifyElementFromStoragePool',
storageConfigService, ElementName=volumeName,
InPool=poolInstanceName,
ElementType=self.utils.get_num(THINPROVISIONING, '16'),
Size=self.utils.get_num(volumeSize, '64'),
EMCBindElements=False)
LOG.debug("Create Volume: %(volumename)s Return code: %(rc)lu.",
{'volumename': volumeName,
'rc': rc})
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Create Volume: %(volumeName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'volumeName': volumeName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateOrModifyElementFromStoragePool "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
# Find the newly created volume.
volumeDict = self.get_volume_dict_from_job(conn, job['Job'])
return volumeDict, rc
def create_and_get_storage_group(self, conn, controllerConfigService,
storageGroupName, volumeInstanceName,
extraSpecs):
"""Create a storage group and return it.
:param conn: the connection information to the ecom server
:param controllerConfigService: the controller configuration service
:param storageGroupName: the storage group name (String
:param volumeInstanceName: the volume instance name
:param extraSpecs: additional info
:returns: foundStorageGroupInstanceName - instance name of the
default storage group
:raises: VolumeBackendAPIException
"""
startTime = time.time()
@coordination.synchronized("emc-sg-{storageGroupName}")
def do_create_storage_group(storageGroupName):
rc, job = conn.InvokeMethod(
'CreateGroup', controllerConfigService,
GroupName=storageGroupName,
Type=self.utils.get_num(STORAGEGROUPTYPE, '16'),
Members=[volumeInstanceName])
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': storageGroupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateGroup "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
foundStorageGroupInstanceName = self._find_new_storage_group(
conn, job, storageGroupName)
return foundStorageGroupInstanceName
return do_create_storage_group(storageGroupName)
def create_storage_group_no_members(
self, conn, controllerConfigService, groupName, extraSpecs):
"""Create a new storage group that has no members.
:param conn: connection to the ecom server
:param controllerConfigService: the controller configuration service
:param groupName: the proposed group name
:param extraSpecs: additional info
:returns: foundStorageGroupInstanceName
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'CreateGroup', controllerConfigService, GroupName=groupName,
Type=self.utils.get_num(STORAGEGROUPTYPE, '16'),
DeleteWhenBecomesUnassociated=False)
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': groupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateGroup "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
foundStorageGroupInstanceName = self._find_new_storage_group(
conn, job, groupName)
return foundStorageGroupInstanceName
def _find_new_storage_group(
self, conn, maskingGroupDict, storageGroupName):
"""After creating a new storage group find it and return it.
:param conn: connection to the ecom server
:param maskingGroupDict: the maskingGroupDict dict
:param storageGroupName: storage group name (String)
:returns: maskingGroupDict['MaskingGroup']
"""
foundStorageGroupInstanceName = None
if 'MaskingGroup' in maskingGroupDict:
foundStorageGroupInstanceName = maskingGroupDict['MaskingGroup']
return foundStorageGroupInstanceName
def get_volume_dict_from_job(self, conn, jobInstance):
"""Given the jobInstance determine the volume Instance.
:param conn: the ecom connection
:param jobInstance: the instance of a job
:returns: dict -- volumeDict - an instance of a volume
"""
associators = conn.Associators(
jobInstance,
ResultClass='EMC_StorageVolume')
volpath = associators[0].path
volumeDict = {}
volumeDict['classname'] = volpath.classname
keys = {}
keys['CreationClassName'] = volpath['CreationClassName']
keys['SystemName'] = volpath['SystemName']
keys['DeviceID'] = volpath['DeviceID']
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
volumeDict['keybindings'] = keys
return volumeDict
def remove_device_from_storage_group(
self, conn, controllerConfigService, sgInstanceName,
volumeInstanceName, volumeName, extraSpecs):
"""Remove a volume from a storage group.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration service
:param sgInstanceName: the instance name of the storage group
:param volumeInstanceName: the instance name of the volume
:param volumeName: the volume name (String)
:param extraSpecs: additional info
:returns: int -- the return code of the job
:raises: VolumeBackendAPIException
"""
try:
storageGroupInstance = conn.GetInstance(sgInstanceName)
except Exception:
exceptionMessage = (_(
"Unable to get the name of the storage group."))
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
def do_remove_volume_from_sg():
startTime = time.time()
rc, jobDict = conn.InvokeMethod('RemoveMembers',
controllerConfigService,
MaskingGroup=sgInstanceName,
Members=[volumeInstanceName])
if rc != 0:
rc, errorDesc = self.utils.wait_for_job_complete(conn, jobDict,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error removing volume %(vol)s from %(sg). %(error)s.")
% {'vol': volumeName,
'sg': storageGroupInstance['ElementName'],
'error': errorDesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod RemoveMembers "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc
return do_remove_volume_from_sg()
def add_members_to_masking_group(
self, conn, controllerConfigService, storageGroupInstanceName,
volumeInstanceName, volumeName, extraSpecs):
"""Add a member to a masking group group.
:param conn: the connection to the ecom server
:param controllerConfigService: the controller configuration service
:param storageGroupInstanceName: the instance name of the storage group
:param volumeInstanceName: the instance name of the volume
:param volumeName: the volume name (String)
:param extraSpecs: additional info
:raises: VolumeBackendAPIException
"""
try:
storageGroupInstance = conn.GetInstance(storageGroupInstanceName)
except Exception:
exceptionMessage = (_(
"Unable to get the name of the storage group."))
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
@coordination.synchronized('emc-sg-'
'{storageGroupInstance[ElementName]}')
def do_add_volume_to_sg(storageGroupInstance):
startTime = time.time()
rc, job = conn.InvokeMethod(
'AddMembers', controllerConfigService,
MaskingGroup=storageGroupInstanceName,
Members=[volumeInstanceName])
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error adding volume %(vol)s to %(sg). %(error)s.")
% {'vol': volumeName,
'sg': storageGroupInstance['ElementName'],
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod AddMembers "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc
return do_add_volume_to_sg(storageGroupInstance)
def unbind_volume_from_storage_pool(
self, conn, storageConfigService,
volumeInstanceName, volumeName, extraSpecs):
"""Unbind a volume from a pool and return the unbound volume.
:param conn: the connection information to the ecom server
:param storageConfigService: the storage configuration service
instance name
:param volumeInstanceName: the volume instance name
:param volumeName: the volume name
:param extraSpecs: additional info
:returns: int -- return code
:returns: the job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'EMCUnBindElement',
storageConfigService,
TheElement=volumeInstanceName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error unbinding volume %(vol)s from pool. %(error)s.")
% {'vol': volumeName, 'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod EMCUnBindElement "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def modify_composite_volume(
self, conn, elementCompositionService, theVolumeInstanceName,
inVolumeInstanceName, extraSpecs):
"""Given a composite volume add a storage volume to it.
:param conn: the connection to the ecom
:param elementCompositionService: the element composition service
:param theVolumeInstanceName: the existing composite volume
:param inVolumeInstanceName: the volume you wish to add to the
composite volume
:param extraSpecs: additional info
:returns: int -- rc - return code
:returns: the job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'CreateOrModifyCompositeElement',
elementCompositionService,
TheElement=theVolumeInstanceName,
InElements=[inVolumeInstanceName])
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error adding volume to composite volume. "
"Error is: %(error)s.")
% {'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateOrModifyCompositeElement "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def create_composite_volume(
self, conn, elementCompositionService, volumeSize, volumeName,
poolInstanceName, compositeType, numMembers, extraSpecs):
"""Create a new volume using the auto meta feature.
:param conn: connection to the ecom server
:param elementCompositionService: the element composition service
:param volumeSize: the size of the volume
:param volumeName: user friendly name
:param poolInstanceName: the pool to bind the composite volume to
:param compositeType: the proposed composite type of the volume
e.g striped/concatenated
:param numMembers: the number of meta members to make up the composite.
If it is 1 then a non composite is created
:param extraSpecs: additional info
:returns: dict -- volumeDict
:returns: int -- return code
:raises: VolumeBackendAPIException
"""
startTime = time.time()
newMembers = 2
LOG.debug(
"Parameters for CreateOrModifyCompositeElement: "
"elementCompositionService: %(elementCompositionService)s "
"provisioning: %(provisioning)lu "
"volumeSize: %(volumeSize)s "
"newMembers: %(newMembers)lu "
"poolInstanceName: %(poolInstanceName)s "
"compositeType: %(compositeType)lu "
"numMembers: %(numMembers)s.",
{'elementCompositionService': elementCompositionService,
'provisioning': THINPROVISIONINGCOMPOSITE,
'volumeSize': volumeSize,
'newMembers': newMembers,
'poolInstanceName': poolInstanceName,
'compositeType': compositeType,
'numMembers': numMembers})
rc, job = conn.InvokeMethod(
'CreateOrModifyCompositeElement', elementCompositionService,
ElementName=volumeName,
ElementType=self.utils.get_num(THINPROVISIONINGCOMPOSITE, '16'),
Size=self.utils.get_num(volumeSize, '64'),
ElementSource=self.utils.get_num(newMembers, '16'),
EMCInPools=[poolInstanceName],
CompositeType=self.utils.get_num(compositeType, '16'),
EMCNumberOfMembers=self.utils.get_num(numMembers, '32'))
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Create Volume: %(volumename)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'volumename': volumeName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateOrModifyCompositeElement "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
# Find the newly created volume.
volumeDict = self.get_volume_dict_from_job(conn, job['Job'])
return volumeDict, rc
def create_new_composite_volume(
self, conn, elementCompositionService, compositeHeadInstanceName,
compositeMemberInstanceName, compositeType, extraSpecs):
"""Creates a new composite volume.
Given a bound composite head and an unbound composite member
create a new composite volume.
:param conn: connection to the ecom server
:param elementCompositionService: the element composition service
:param compositeHeadInstanceName: the composite head. This can be bound
:param compositeMemberInstanceName: the composite member. This must be
unbound
:param compositeType: the composite type e.g striped or concatenated
:param extraSpecs: additional info
:returns: int -- return code
:returns: the job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'CreateOrModifyCompositeElement', elementCompositionService,
ElementType=self.utils.get_num('2', '16'),
InElements=(
[compositeHeadInstanceName, compositeMemberInstanceName]),
CompositeType=self.utils.get_num(compositeType, '16'))
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Creating new composite Volume Return code: "
"%(rc)lu. Error: %(error)s.")
% {'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateOrModifyCompositeElement "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def _migrate_volume(
self, conn, storageRelocationServiceInstanceName,
volumeInstanceName, targetPoolInstanceName, extraSpecs):
"""Migrate a volume to another pool.
:param conn: the connection to the ecom server
:param storageRelocationServiceInstanceName: the storage relocation
service
:param volumeInstanceName: the volume to be migrated
:param targetPoolInstanceName: the target pool to migrate the volume to
:param extraSpecs: additional info
:returns: int -- return code
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'RelocateStorageVolumesToStoragePool',
storageRelocationServiceInstanceName,
TheElements=[volumeInstanceName],
TargetPool=targetPoolInstanceName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Migrating volume from one pool to another. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod RelocateStorageVolumesToStoragePool "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc
def migrate_volume_to_storage_pool(
self, conn, storageRelocationServiceInstanceName,
volumeInstanceName, targetPoolInstanceName, extraSpecs):
"""Given the storage system name, get the storage relocation service.
:param conn: the connection to the ecom server
:param storageRelocationServiceInstanceName: the storage relocation
service
:param volumeInstanceName: the volume to be migrated
:param targetPoolInstanceName: the target pool to migrate the
volume to.
:param extraSpecs: additional info
:returns: int -- rc, return code
:raises: VolumeBackendAPIException
"""
LOG.debug(
"Volume instance name is %(volumeInstanceName)s. "
"Pool instance name is : %(targetPoolInstanceName)s. ",
{'volumeInstanceName': volumeInstanceName,
'targetPoolInstanceName': targetPoolInstanceName})
rc = -1
try:
rc = self._migrate_volume(
conn, storageRelocationServiceInstanceName,
volumeInstanceName, targetPoolInstanceName, extraSpecs)
except Exception as ex:
if 'source of a migration session' in six.text_type(ex):
try:
rc = self._terminate_migrate_session(
conn, volumeInstanceName, extraSpecs)
except Exception:
exceptionMessage = (_(
"Failed to terminate migrate session."))
LOG.exception(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
try:
rc = self._migrate_volume(
conn, storageRelocationServiceInstanceName,
volumeInstanceName, targetPoolInstanceName,
extraSpecs)
except Exception:
exceptionMessage = (_(
"Failed to migrate volume for the second time."))
LOG.exception(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
else:
exceptionMessage = (_(
"Failed to migrate volume for the first time."))
LOG.exception(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
return rc
def _terminate_migrate_session(self, conn, volumeInstanceName,
extraSpecs):
"""Given the volume instance terminate a migrate session.
:param conn: the connection to the ecom server
:param volumeInstanceName: the volume to be migrated
:param extraSpecs: additional info
:returns: int -- return code
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'RequestStateChange', volumeInstanceName,
RequestedState=self.utils.get_num(32769, '16'))
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Terminating migrate session. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod RequestStateChange "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc
def create_element_replica(
self, conn, repServiceInstanceName, cloneName,
sourceName, sourceInstance, targetInstance, extraSpecs,
copyOnWrite=False):
"""Make SMI-S call to create replica for source element.
:param conn: the connection to the ecom server
:param repServiceInstanceName: replication service
:param cloneName: replica name
:param sourceName: source volume name
:param sourceInstance: source volume instance
:param targetInstance: the target instance
:param extraSpecs: additional info
:param copyOnWrite: optional
:returns: int -- return code
:returns: job object of the replica creation operation
:raises: VolumeBackendAPIException
"""
if copyOnWrite:
startTime = time.time()
# ReplicationType 10 - Synchronous Clone Local.
# Set DesiredCopyMethodology to Copy-On-Write (6).
rsdInstance = self.utils.set_copy_methodology_in_rsd(
conn, repServiceInstanceName, SYNC_CLONE_LOCAL,
COPY_ON_WRITE, extraSpecs)
# SyncType 8 - Clone.
# ReplicationSettingData.DesiredCopyMethodology Copy-On-Write (6).
rc, job = conn.InvokeMethod(
'CreateElementReplica', repServiceInstanceName,
ElementName=cloneName,
SyncType=self.utils.get_num(TF_CLONE, '16'),
ReplicationSettingData=rsdInstance,
SourceElement=sourceInstance.path)
else:
startTime = time.time()
if targetInstance is None:
rc, job = conn.InvokeMethod(
'CreateElementReplica', repServiceInstanceName,
ElementName=cloneName,
SyncType=self.utils.get_num(TF_CLONE, '16'),
SourceElement=sourceInstance.path)
else:
rc, job = conn.InvokeMethod(
'CreateElementReplica', repServiceInstanceName,
ElementName=cloneName,
SyncType=self.utils.get_num(TF_CLONE, '16'),
SourceElement=sourceInstance.path,
TargetElement=targetInstance.path)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error Create Cloned Volume: "
"Volume: %(cloneName)s Source Volume:"
"%(sourceName)s. Return code: %(rc)lu. "
"Error: %(error)s.")
% {'cloneName': cloneName,
'sourceName': sourceName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateElementReplica "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def delete_clone_relationship(
self, conn, repServiceInstanceName, syncInstanceName, extraSpecs,
force=False):
"""Deletes the relationship between the clone and source volume.
Makes an SMI-S call to break clone relationship between the clone
volume and the source.
8/Detach - Delete the synchronization between two storage objects.
Treat the objects as independent after the synchronization is deleted.
:param conn: the connection to the ecom server
:param repServiceInstanceName: instance name of the replication service
:param syncInstanceName: instance name of the
SE_StorageSynchronized_SV_SV object
:param extraSpecs: additional info
:param force: optional param
:returns: int -- return code
:returns: job object of the replica creation operation
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'ModifyReplicaSynchronization', repServiceInstanceName,
Operation=self.utils.get_num(8, '16'),
Synchronization=syncInstanceName,
Force=force)
LOG.debug("Delete clone relationship: Sync Name: %(syncName)s "
"Return code: %(rc)lu.",
{'syncName': syncInstanceName,
'rc': rc})
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Error break clone relationship: "
"Sync Name: %(syncName)s "
"Return code: %(rc)lu. Error: %(error)s.")
% {'syncName': syncInstanceName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod ModifyReplicaSynchronization "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def create_consistency_group(
self, conn, replicationService, consistencyGroupName, extraSpecs):
"""Create a new consistency group.
:param conn: the connection to the ecom server
:param replicationService: the replication Service
:param consistencyGroupName: the CG group name
:param extraSpecs: additional info
:returns: int -- return code
:returns: job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'CreateGroup',
replicationService,
GroupName=consistencyGroupName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Failed to create consistency group: "
"%(consistencyGroupName)s "
"Return code: %(rc)lu. Error: %(error)s.")
% {'consistencyGroupName': consistencyGroupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod CreateGroup "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def delete_consistency_group(
self, conn, replicationService, cgInstanceName,
consistencyGroupName, extraSpecs):
"""Delete a consistency group.
:param conn: the connection to the ecom server
:param replicationService: the replication Service
:param cgInstanceName: the CG instance name
:param consistencyGroupName: the CG group name
:param extraSpecs: additional info
:returns: int -- return code
:returns: job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
rc, job = conn.InvokeMethod(
'DeleteGroup',
replicationService,
ReplicationGroup=cgInstanceName,
RemoveElements=True)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Failed to delete consistency group: "
"%(consistencyGroupName)s "
"Return code: %(rc)lu. Error: %(error)s.")
% {'consistencyGroupName': consistencyGroupName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod DeleteGroup "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def add_volume_to_cg(
self, conn, replicationService, cgInstanceName,
volumeInstanceName, cgName, volumeName, extraSpecs):
"""Add a volume to a consistency group.
:param conn: the connection to the ecom server
:param replicationService: the replication Service
:param cgInstanceName: the CG instance name
:param volumeInstanceName: the volume instance name
:param cgName: the CG group name
:param volumeName: the volume name
:param extraSpecs: additional info
:returns: int -- return code
:returns: job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
if isinstance(volumeInstanceName, list):
theElements = volumeInstanceName
volumeName = 'Bulk Add'
else:
theElements = [volumeInstanceName]
rc, job = conn.InvokeMethod(
'AddMembers',
replicationService,
Members=theElements,
ReplicationGroup=cgInstanceName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Failed to add volume %(volumeName)s "
"to consistency group %(cgName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'volumeName': volumeName,
'cgName': cgName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod AddMembers "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def remove_volume_from_cg(
self, conn, replicationService, cgInstanceName,
volumeInstanceName, cgName, volumeName, extraSpecs):
"""Remove a volume from a consistency group.
:param conn: the connection to the ecom server
:param replicationService: the replication Service
:param cgInstanceName: the CG instance name
:param volumeInstanceName: the volume instance name
:param cgName: the CG group name
:param volumeName: the volume name
:param extraSpecs: additional info
:returns: int -- return code
:returns: job object
:raises: VolumeBackendAPIException
"""
startTime = time.time()
if isinstance(volumeInstanceName, list):
theElements = volumeInstanceName
volumeName = 'Bulk Remove'
else:
theElements = [volumeInstanceName]
rc, job = conn.InvokeMethod(
'RemoveMembers',
replicationService,
Members=theElements,
ReplicationGroup=cgInstanceName)
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMessage = (_(
"Failed to remove volume %(volumeName)s "
"from consistency group %(cgName)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'volumeName': volumeName,
'cgName': cgName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
LOG.debug("InvokeMethod RemoveMembers "
"took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(startTime,
time.time())})
return rc, job
def create_group_replica(
self, conn, replicationService,
srcGroupInstanceName, tgtGroupInstanceName, relationName,
extraSpecs):
"""Make SMI-S call to create replica for source group.
:param conn: the connection to the ecom server
:param replicationService: replication service
:param srcGroupInstanceName: source group instance name
:param tgtGroupInstanceName: target group instance name
:param relationName: relation name
:param extraSpecs: additional info
:returns: int -- return code
:returns: job object of the replica creation operation
:raises: VolumeBackendAPIException
"""
LOG.debug(
"Parameters for CreateGroupReplica: "
"replicationService: %(replicationService)s "
"RelationName: %(relationName)s "
"sourceGroup: %(srcGroup)s "
"targetGroup: %(tgtGroup)s.",
{'replicationService': replicationService,
'relationName': relationName,
'srcGroup': srcGroupInstanceName,
'tgtGroup': tgtGroupInstanceName})
# 8 for clone.
rc, job = conn.InvokeMethod(
'CreateGroupReplica',
replicationService,
RelationshipName=relationName,
SourceGroup=srcGroupInstanceName,
TargetGroup=tgtGroupInstanceName,
SyncType=self.utils.get_num(8, '16'),
WaitForCopyState=self.utils.get_num(4, '16'))
if rc != 0:
rc, errordesc = self.utils.wait_for_job_complete(conn, job,
extraSpecs)
if rc != 0:
exceptionMsg = (_("Error CreateGroupReplica: "
"source: %(source)s target: %(target)s. "
"Return code: %(rc)lu. Error: %(error)s.")
% {'source': srcGroupInstanceName,
'target': tgtGroupInstanceName,
'rc': rc,
'error': errordesc})
LOG.error(exceptionMsg)
raise exception.VolumeBackendAPIException(data=exceptionMsg)
return rc, job