Dell PowerMax: Fix SnapVx unlink failure

Dell PowerMax SnapVx unlink fails as the linked device
is not yet fully defined.
This patch fixed the issue by checking the new configuration
'snapvx_unlink_symforce' and the linked device 'defined' status
to determine the value of 'symforce' in the payload of
SnapVx unlink restcall.

Closes-Bug: #2045230
Change-Id: I614f6aef2d4da76c417b4a143ab80e4a5f716dcd
This commit is contained in:
Yian Zong 2023-11-30 09:30:19 +00:00
parent 0581e8cbd6
commit 03ac369854
9 changed files with 116 additions and 28 deletions

View File

@ -171,7 +171,7 @@ class PowerMaxProvisionTest(test.TestCase):
mock_unlink.assert_called_once_with( mock_unlink.assert_called_once_with(
array, source_device_id, target_device_id, array, source_device_id, target_device_id,
snap_name, extra_specs, snap_id=self.data.snap_id, snap_name, extra_specs, snap_id=self.data.snap_id,
list_volume_pairs=None, loop=True) list_volume_pairs=None, loop=True, symforce=False)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall) new=test_utils.ZeroIntervalLoopingCall)
@ -184,7 +184,8 @@ class PowerMaxProvisionTest(test.TestCase):
mock_mod.assert_called_once_with( mock_mod.assert_called_once_with(
self.data.array, self.data.device_id, self.data.device_id2, self.data.array, self.data.device_id, self.data.device_id2,
self.data.snap_location['snap_name'], self.data.extra_specs, self.data.snap_location['snap_name'], self.data.extra_specs,
snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True) snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True,
symforce=False)
mock_mod.reset_mock() mock_mod.reset_mock()
self.provision._unlink_volume( self.provision._unlink_volume(
@ -194,7 +195,8 @@ class PowerMaxProvisionTest(test.TestCase):
mock_mod.assert_called_once_with( mock_mod.assert_called_once_with(
self.data.array, self.data.device_id, self.data.device_id2, self.data.array, self.data.device_id, self.data.device_id2,
self.data.snap_location['snap_name'], self.data.extra_specs, self.data.snap_location['snap_name'], self.data.extra_specs,
snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True) snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True,
symforce=False)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall) new=test_utils.ZeroIntervalLoopingCall)
@ -556,9 +558,12 @@ class PowerMaxProvisionTest(test.TestCase):
@mock.patch.object( @mock.patch.object(
rest.PowerMaxRest, 'get_snap_linked_device_list', rest.PowerMaxRest, 'get_snap_linked_device_list',
side_effect=[[{'targetDevice': tpd.PowerMaxData.device_id2}], side_effect=[[{'targetDevice': tpd.PowerMaxData.device_id2,
[{'targetDevice': tpd.PowerMaxData.device_id2}, 'defined': False}],
{'targetDevice': tpd.PowerMaxData.device_id3}]]) [{'targetDevice': tpd.PowerMaxData.device_id2,
'defined': False},
{'targetDevice': tpd.PowerMaxData.device_id3,
'defined': False}]])
@mock.patch.object(provision.PowerMaxProvision, '_unlink_volume') @mock.patch.object(provision.PowerMaxProvision, '_unlink_volume')
def test_delete_volume_snap_check_for_links(self, mock_unlink, mock_tgts): def test_delete_volume_snap_check_for_links(self, mock_unlink, mock_tgts):
self.provision.delete_volume_snap_check_for_links( self.provision.delete_volume_snap_check_for_links(
@ -568,7 +573,8 @@ class PowerMaxProvisionTest(test.TestCase):
self.data.array, "", "", self.data.test_snapshot_snap_name, self.data.array, "", "", self.data.test_snapshot_snap_name,
self.data.extra_specs, snap_id=self.data.snap_id, self.data.extra_specs, snap_id=self.data.snap_id,
list_volume_pairs=[ list_volume_pairs=[
(self.data.device_id, tpd.PowerMaxData.device_id2)]) (self.data.device_id, tpd.PowerMaxData.device_id2)],
symforce=False)
mock_unlink.reset_mock() mock_unlink.reset_mock()
self.provision.delete_volume_snap_check_for_links( self.provision.delete_volume_snap_check_for_links(
self.data.array, self.data.test_snapshot_snap_name, self.data.array, self.data.test_snapshot_snap_name,

View File

@ -154,7 +154,12 @@ powermax_opts = [
cfg.IntOpt(utils.REST_API_READ_TIMEOUT, cfg.IntOpt(utils.REST_API_READ_TIMEOUT,
default=30, min=1, default=30, min=1,
help='Use this value to specify read ' help='Use this value to specify read '
'timeout value (in seconds) for rest call.')] 'timeout value (in seconds) for rest call.'),
cfg.BoolOpt(utils.SNAPVX_UNLINK_SYMFORCE,
default=False,
help='Enable SnapVx unlink symforce, which forces '
'the operation to execute when normally it is rejected.'),
]
CONF.register_opts(powermax_opts, group=configuration.SHARED_CONF_GROUP) CONF.register_opts(powermax_opts, group=configuration.SHARED_CONF_GROUP)
@ -195,11 +200,8 @@ class PowerMaxCommon(object):
self.rest = rest.PowerMaxRest() self.rest = rest.PowerMaxRest()
self.utils = utils.PowerMaxUtils() self.utils = utils.PowerMaxUtils()
self.masking = masking.PowerMaxMasking(prtcl, self.rest)
self.provision = provision.PowerMaxProvision(self.rest)
self.volume_metadata = volume_metadata.PowerMaxVolumeMetadata( self.volume_metadata = volume_metadata.PowerMaxVolumeMetadata(
self.rest, version, LOG.isEnabledFor(logging.DEBUG)) self.rest, version, LOG.isEnabledFor(logging.DEBUG))
self.migrate = migrate.PowerMaxMigrate(prtcl, self.rest)
# Configuration/Attributes # Configuration/Attributes
self.protocol = prtcl self.protocol = prtcl
@ -220,6 +222,14 @@ class PowerMaxCommon(object):
if active_backend_id == utils.PMAX_FAILOVER_START_ARRAY_PROMOTION: if active_backend_id == utils.PMAX_FAILOVER_START_ARRAY_PROMOTION:
self.promotion = True self.promotion = True
# Init provision, masking and migrate instances
self.provision = provision.PowerMaxProvision(
self.rest, self.configuration)
self.masking = masking.PowerMaxMasking(
prtcl, self.rest, self.configuration)
self.migrate = migrate.PowerMaxMigrate(
prtcl, self.rest, self.configuration)
# Gather environment info # Gather environment info
self._get_replication_info() self._get_replication_info()
self._get_u4p_failover_info() self._get_u4p_failover_info()

View File

@ -39,11 +39,12 @@ class PowerMaxMasking(object):
Masking code to dynamically create a masking view. Masking code to dynamically create a masking view.
It supports VMAX 3, All Flash and PowerMax arrays. It supports VMAX 3, All Flash and PowerMax arrays.
""" """
def __init__(self, prtcl, rest): def __init__(self, prtcl, rest, configuration):
self.protocol = prtcl self.protocol = prtcl
self.utils = utils.PowerMaxUtils() self.utils = utils.PowerMaxUtils()
self.rest = rest self.rest = rest
self.provision = provision.PowerMaxProvision(self.rest) self.provision = provision.PowerMaxProvision(self.rest,
configuration)
def setup_masking_view( def setup_masking_view(
self, serial_number, volume, masking_view_dict, extra_specs): self, serial_number, volume, masking_view_dict, extra_specs):

View File

@ -34,11 +34,11 @@ class PowerMaxMigrate(object):
It supports VMAX 3 and VMAX All Flash and PowerMax arrays. It supports VMAX 3 and VMAX All Flash and PowerMax arrays.
""" """
def __init__(self, prtcl, rest): def __init__(self, prtcl, rest, configuration):
self.rest = rest self.rest = rest
self.utils = utils.PowerMaxUtils() self.utils = utils.PowerMaxUtils()
self.masking = masking.PowerMaxMasking(prtcl, self.rest) self.masking = masking.PowerMaxMasking(prtcl, self.rest, configuration)
self.provision = provision.PowerMaxProvision(self.rest) self.provision = provision.PowerMaxProvision(self.rest, configuration)
def do_migrate_if_candidate( def do_migrate_if_candidate(
self, array, srp, device_id, volume, connector): self, array, srp, device_id, volume, connector):

View File

@ -36,9 +36,11 @@ class PowerMaxProvision(object):
It supports VMAX 3, All Flash and PowerMax arrays. It supports VMAX 3, All Flash and PowerMax arrays.
""" """
def __init__(self, rest): def __init__(self, rest, configuration):
self.utils = utils.PowerMaxUtils() self.utils = utils.PowerMaxUtils()
self.rest = rest self.rest = rest
self.snapvx_unlink_symforce = configuration.safe_get(
utils.SNAPVX_UNLINK_SYMFORCE) or False
def create_storage_group( def create_storage_group(
self, array, storagegroup_name, srp, slo, workload, self, array, storagegroup_name, srp, slo, workload,
@ -183,6 +185,9 @@ class PowerMaxProvision(object):
{'delta': self.utils.get_time_delta(start_time, {'delta': self.utils.get_time_delta(start_time,
time.time())}) time.time())})
def _is_symforce_enabled(self, defined):
return self.snapvx_unlink_symforce and (not defined)
def unlink_snapvx_tgt_volume( def unlink_snapvx_tgt_volume(
self, array, target_device_id, source_device_id, snap_name, self, array, target_device_id, source_device_id, snap_name,
extra_specs, snap_id, loop=True): extra_specs, snap_id, loop=True):
@ -197,20 +202,33 @@ class PowerMaxProvision(object):
:param loop: if looping call is required for handling retries :param loop: if looping call is required for handling retries
""" """
@coordination.synchronized("emc-snapvx-{src_device_id}") @coordination.synchronized("emc-snapvx-{src_device_id}")
def do_unlink_volume(src_device_id): def do_unlink_volume(src_device_id, symforce=False):
LOG.debug("Break snap vx link relationship between: %(src)s " LOG.debug("Break snap vx link relationship between: %(src)s "
"and: %(tgt)s.", "and: %(tgt)s.",
{'src': src_device_id, 'tgt': target_device_id}) {'src': src_device_id, 'tgt': target_device_id})
self._unlink_volume(array, src_device_id, target_device_id, self._unlink_volume(array, src_device_id, target_device_id,
snap_name, extra_specs, snap_id=snap_id, snap_name, extra_specs, snap_id=snap_id,
list_volume_pairs=None, loop=loop) list_volume_pairs=None, loop=loop,
symforce=symforce)
do_unlink_volume(source_device_id) # Get the link defined status
defined = True
linked_list = self.rest.get_snap_linked_device_list(
array, source_device_id, snap_name, snap_id)
for link in linked_list:
if target_device_id == link['targetDevice']:
if not link['defined']:
defined = False
break
LOG.debug("The link defined status: %s", defined)
do_unlink_volume(
source_device_id, symforce=self._is_symforce_enabled(defined))
def _unlink_volume( def _unlink_volume(
self, array, source_device_id, target_device_id, snap_name, self, array, source_device_id, target_device_id, snap_name,
extra_specs, snap_id=None, list_volume_pairs=None, loop=True): extra_specs, snap_id=None, list_volume_pairs=None, loop=True,
symforce=False):
"""Unlink a target volume from its source volume. """Unlink a target volume from its source volume.
:param array: the array serial number :param array: the array serial number
@ -221,6 +239,7 @@ class PowerMaxProvision(object):
:param snap_id: the unique snap id of the SnapVX :param snap_id: the unique snap id of the SnapVX
:param list_volume_pairs: list of volume pairs, optional :param list_volume_pairs: list of volume pairs, optional
:param loop: if looping call is required for handling retries :param loop: if looping call is required for handling retries
:param symforce: if symforce is enabled
:returns: return code :returns: return code
""" """
def _unlink_vol(): def _unlink_vol():
@ -234,7 +253,8 @@ class PowerMaxProvision(object):
if not kwargs['modify_vol_success']: if not kwargs['modify_vol_success']:
self.rest.modify_volume_snap( self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name, array, source_device_id, target_device_id, snap_name,
extra_specs, snap_id=snap_id, unlink=True, extra_specs, snap_id=snap_id,
unlink=True, symforce=symforce,
list_volume_pairs=list_volume_pairs) list_volume_pairs=list_volume_pairs)
kwargs['modify_vol_success'] = True kwargs['modify_vol_success'] = True
except exception.VolumeBackendAPIException: except exception.VolumeBackendAPIException:
@ -250,7 +270,8 @@ class PowerMaxProvision(object):
if not loop: if not loop:
self.rest.modify_volume_snap( self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name, array, source_device_id, target_device_id, snap_name,
extra_specs, snap_id=snap_id, unlink=True, extra_specs, snap_id=snap_id,
unlink=True, symforce=symforce,
list_volume_pairs=list_volume_pairs) list_volume_pairs=list_volume_pairs)
else: else:
kwargs = {'retries': 0, kwargs = {'retries': 0,
@ -387,6 +408,7 @@ class PowerMaxProvision(object):
:param snap_id: the unique snap id of the SnapVX :param snap_id: the unique snap id of the SnapVX
""" """
list_device_pairs = [] list_device_pairs = []
list_device_pairs_defined = True
if not isinstance(source_devices, list): if not isinstance(source_devices, list):
source_devices = [source_devices] source_devices = [source_devices]
for source_device in source_devices: for source_device in source_devices:
@ -398,6 +420,8 @@ class PowerMaxProvision(object):
if len(linked_list) == 1: if len(linked_list) == 1:
target_device = linked_list[0]['targetDevice'] target_device = linked_list[0]['targetDevice']
list_device_pairs.append((source_device, target_device)) list_device_pairs.append((source_device, target_device))
if not linked_list[0]['defined']:
list_device_pairs_defined = False
else: else:
for link in linked_list: for link in linked_list:
# If a single source volume has multiple targets, # If a single source volume has multiple targets,
@ -405,11 +429,13 @@ class PowerMaxProvision(object):
target_device = link['targetDevice'] target_device = link['targetDevice']
self._unlink_volume( self._unlink_volume(
array, source_device, target_device, snap_name, array, source_device, target_device, snap_name,
extra_specs, snap_id=snap_id) extra_specs, snap_id=snap_id,
symforce=self._is_symforce_enabled(link['defined']))
if list_device_pairs: if list_device_pairs:
self._unlink_volume( self._unlink_volume(
array, "", "", snap_name, extra_specs, snap_id=snap_id, array, "", "", snap_name, extra_specs, snap_id=snap_id,
list_volume_pairs=list_device_pairs) list_volume_pairs=list_device_pairs,
symforce=self._is_symforce_enabled(list_device_pairs_defined))
if source_devices: if source_devices:
self.delete_volume_snap( self.delete_volume_snap(
array, snap_name, source_devices, snap_id, restored=False) array, snap_name, source_devices, snap_id, restored=False)
@ -703,6 +729,14 @@ class PowerMaxProvision(object):
self.rest.modify_volume_snap( self.rest.modify_volume_snap(
array, None, None, snap_name, extra_specs, snap_id=snap_id, array, None, None, snap_name, extra_specs, snap_id=snap_id,
link=True, list_volume_pairs=list_volume_pairs) link=True, list_volume_pairs=list_volume_pairs)
# Get the link defined status
defined = True
for src, dst in list_volume_pairs:
linked_list = self.rest.get_snap_linked_device_list(
array, src, snap_name, snap_id)
if not linked_list[0]['defined']:
defined = False
break
# Unlink the snapshot # Unlink the snapshot
LOG.debug("Unlinking Snap Vx snapshot: source group: %(srcGroup)s " LOG.debug("Unlinking Snap Vx snapshot: source group: %(srcGroup)s "
"targetGroup: %(tgtGroup)s.", "targetGroup: %(tgtGroup)s.",
@ -710,7 +744,8 @@ class PowerMaxProvision(object):
'tgtGroup': target_group_name}) 'tgtGroup': target_group_name})
self._unlink_volume( self._unlink_volume(
array, None, None, snap_name, extra_specs, snap_id=snap_id, array, None, None, snap_name, extra_specs, snap_id=snap_id,
list_volume_pairs=list_volume_pairs) list_volume_pairs=list_volume_pairs,
symforce=self._is_symforce_enabled(defined))
# Delete the snapshot if necessary # Delete the snapshot if necessary
if delete_snapshot: if delete_snapshot:
LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s " LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s "

View File

@ -2197,7 +2197,7 @@ class PowerMaxRest(object):
def modify_volume_snap(self, array, source_id, target_id, snap_name, def modify_volume_snap(self, array, source_id, target_id, snap_name,
extra_specs, snap_id=None, link=False, unlink=False, extra_specs, snap_id=None, link=False, unlink=False,
rename=False, new_snap_name=None, restore=False, rename=False, new_snap_name=None, restore=False,
list_volume_pairs=None, copy=False): list_volume_pairs=None, copy=False, symforce=False):
"""Modify a snapvx snapshot """Modify a snapvx snapshot
:param array: the array serial number :param array: the array serial number
@ -2248,7 +2248,7 @@ class PowerMaxRest(object):
"copy": copy, "action": action, "copy": copy, "action": action,
"star": 'false', "force": force, "star": 'false', "force": force,
"exact": 'false', "remote": 'false', "exact": 'false', "remote": 'false',
"symforce": 'false'} "symforce": str(symforce).lower()}
elif action == "Rename": elif action == "Rename":
operation = 'Rename snapVx snapshot' operation = 'Rename snapVx snapshot'
payload = {"deviceNameListSource": [{"name": source_id}], payload = {"deviceNameListSource": [{"name": source_id}],

View File

@ -179,6 +179,7 @@ LOAD_LOOKBACK = 'load_look_back'
LOAD_LOOKBACK_RT = 'load_look_back_real_time' LOAD_LOOKBACK_RT = 'load_look_back_real_time'
PORT_GROUP_LOAD_METRIC = 'port_group_load_metric' PORT_GROUP_LOAD_METRIC = 'port_group_load_metric'
PORT_LOAD_METRIC = 'port_load_metric' PORT_LOAD_METRIC = 'port_load_metric'
SNAPVX_UNLINK_SYMFORCE = 'snapvx_unlink_symforce'
# One minute in milliseconds # One minute in milliseconds
ONE_MINUTE = 60000 ONE_MINUTE = 60000

View File

@ -3243,6 +3243,9 @@ options are detailed in the table below.
| | | | | load calculation, minimum of 1 | | | | | | load calculation, minimum of 1 |
| | | | | maximum of 60 (24 hours). | | | | | | maximum of 60 (24 hours). |
+-----------------------------+----------------+-----------------+----------------------------------------+ +-----------------------------+----------------+-----------------+----------------------------------------+
| ``snapvx_unlink_symforce`` | ``True/False`` | ``False`` | | Enable/disable symforce |
| | | | | for SnapVx unlink. |
+-----------------------------+----------------+-----------------+----------------------------------------+
| ``port_group_load_metric`` | See below | ``PercentBusy`` | | Metric used for port group load | | ``port_group_load_metric`` | See below | ``PercentBusy`` | | Metric used for port group load |
| | | | | calculation. | | | | | | calculation. |
+-----------------------------+----------------+-----------------+----------------------------------------+ +-----------------------------+----------------+-----------------+----------------------------------------+

View File

@ -0,0 +1,32 @@
---
fixes:
- |
Dell PowerMax Driver `Bug #2045230
<https://bugs.launchpad.net/cinder/+bug/2045230>`_: Fixed
the issue that Dell PowerMax SnapVx link fails as the linked device is not
yet fully defined.
Previously, the below operations could fail if the linked device was not
yet fully defined at the time of the call.
Now, when ``snapvx_unlink_symforce`` is enabled, those operations are not
interrupted by not fully defined devices.
By default, ``snapvx_unlink_symforce`` is ``False``. Use extreme caution
with this option. If used when a link is copy in progress or when a restore
is restore in progress, this will cause an incomplete copy and data on the
copy target would not be usable.
Impacted operations:
* Clone a volume
* Create a volume from a snapshot
* Create volume snapshots
* Delete volume snapshots
* Revert volume to snapshot
* Create generic volume group from source
* Unmanage volumes
upgrade:
- |
Dell PowerMax Driver: introduced a new configuration option,
``snapvx_unlink_symforce``, to address Bug #2045230. See the Bug Fixes
section for details.