VMAX Driver - Fix for multiple clones failure

Fix to ensure that the snapvx snapshots are cleaned
up periodically so that the max limit for snapvx snapshots
is not hit while creating multiple clones of a single volume

Change-Id: Idca6bb86fb0ee873093e4605b24fbc030b037854
Closes-bug: 1778947
This commit is contained in:
Kumar Prashant 2018-06-23 01:49:29 +05:30
parent 92fbe37087
commit 40c563a841
6 changed files with 386 additions and 99 deletions

View File

@ -574,13 +574,17 @@ class VMAXCommonData(object):
{"srcSnapshotGenInfo": [
{"snapshotHeader": {
"snapshotName": "temp-1",
"device": device_id},
"device": device_id,
"generation": "0"},
"lnkSnapshotGenInfo": [
{"targetDevice": device_id2}]}]},
{"targetDevice": device_id2,
"state": "Copied"}]}]},
{"tgtSrcSnapshotGenInfo": {
"snapshotName": "temp-1",
"targetDevice": device_id2,
"sourceDevice": device_id}}],
"sourceDevice": device_id,
"generation": "0",
"state": "Copied"}}],
"snapVXSrc": 'true',
"snapVXTgt": 'true'},
"rdfInfo": {"RDFSession": [
@ -1351,11 +1355,9 @@ class VMAXUtilsTest(test.TestCase):
self.assertEqual('OTHER', other_protocol)
def test_get_temp_snap_name(self):
clone_name = "12345"
source_device_id = self.data.device_id
ref_name = "temp-00001-12345"
snap_name = self.utils.get_temp_snap_name(
clone_name, source_device_id)
ref_name = "temp-00001-snapshot_for_clone"
snap_name = self.utils.get_temp_snap_name(source_device_id)
self.assertEqual(ref_name, snap_name)
def test_get_array_and_device_id(self):
@ -2591,6 +2593,18 @@ class VMAXRestTest(test.TestCase):
self.rest.create_resource.assert_called_once_with(
self.data.array, 'replication', resource_type,
payload, private='/private')
ttl = 1
payload = {"deviceNameListSource": [{"name": device_id}],
"bothSides": 'false', "star": 'false',
"force": 'false', "timeToLive": ttl,
"timeInHours": "true"}
with mock.patch.object(self.rest, 'create_resource',
return_value=(202, self.data.job_list[0])):
self.rest.create_volume_snap(
self.data.array, snap_name, device_id, extra_specs, ttl)
self.rest.create_resource.assert_called_once_with(
self.data.array, 'replication', resource_type,
payload, private='/private')
def test_modify_volume_snap(self):
array = self.data.array
@ -2606,7 +2620,8 @@ class VMAXRestTest(test.TestCase):
"copy": 'true', "action": "",
"star": 'false', "force": 'false',
"exact": 'false', "remote": 'false',
"symforce": 'false', "nocopy": 'false'}
"symforce": 'false', "nocopy": 'false',
"generation": 0}
payload_restore = {"deviceNameListSource": [{"name": source_id}],
"deviceNameListTarget": [{"name": source_id}],
"action": "Restore",
@ -2661,9 +2676,12 @@ class VMAXRestTest(test.TestCase):
snap_name = (self.data.volume_snap_vx
['snapshotSrcs'][0]['snapshotName'])
source_device_id = self.data.device_id
payload = {"deviceNameListSource": [{"name": source_device_id}]}
payload = {"deviceNameListSource": [{"name": source_device_id}],
"generation": 0}
generation = 0
with mock.patch.object(self.rest, 'delete_resource'):
self.rest.delete_volume_snap(array, snap_name, source_device_id)
self.rest.delete_volume_snap(
array, snap_name, source_device_id, generation)
self.rest.delete_resource.assert_called_once_with(
array, 'replication', 'snapshot', snap_name,
payload=payload, private='/private')
@ -2674,7 +2692,7 @@ class VMAXRestTest(test.TestCase):
['snapshotSrcs'][0]['snapshotName'])
source_device_id = self.data.device_id
payload = {"deviceNameListSource": [{"name": source_device_id}],
"restore": True}
"restore": True, "generation": 0}
with mock.patch.object(self.rest, 'delete_resource'):
self.rest.delete_volume_snap(
array, snap_name, source_device_id, restored=True)
@ -2712,9 +2730,25 @@ class VMAXRestTest(test.TestCase):
snap = self.rest.get_volume_snap(array, device_id, snap_name)
self.assertIsNone(snap)
def test_get_snap_linked_device_dict_list(self):
array = self.data.array
snap_name = "temp-snapshot"
device_id = self.data.device_id
snap_list = [{'linked_vols': [
{'target_device': device_id, 'state': "Copied"}],
'snap_name': snap_name, 'generation': "0"}]
ref_snap_list = [{'generation': '0', 'linked_vols': [
{'state': 'Copied', 'target_device': '00001'}]}]
with mock.patch.object(self.rest, '_find_snap_vx_source_sessions',
return_value=snap_list):
snap_dict_list = self.rest._get_snap_linked_device_dict_list(
array, device_id, snap_name)
self.assertEqual(ref_snap_list, snap_dict_list)
def test_get_sync_session(self):
array = self.data.array
source_id = self.data.device_id
generation = 0
target_id = (self.data.volume_snap_vx
['snapshotSrcs'][0]['linkedDevices'][0]['targetDevice'])
snap_name = (self.data.volume_snap_vx
@ -2722,27 +2756,33 @@ class VMAXRestTest(test.TestCase):
ref_sync = (self.data.volume_snap_vx
['snapshotSrcs'][0]['linkedDevices'][0])
sync = self.rest.get_sync_session(
array, source_id, snap_name, target_id)
array, source_id, snap_name, target_id, generation)
self.assertEqual(ref_sync, sync)
def test_find_snap_vx_sessions(self):
array = self.data.array
source_id = self.data.device_id
ref_sessions = [{'snap_name': 'temp-1',
ref_sessions = [{'generation': '0',
'snap_name': 'temp-1',
'source_vol': self.data.device_id,
'target_vol_list': [self.data.device_id2]},
{'snap_name': 'temp-1',
'target_vol_list':
[(self.data.device_id2, "Copied")]},
{'generation': '0',
'snap_name': 'temp-1',
'source_vol': self.data.device_id,
'target_vol_list': [self.data.device_id2]}]
'target_vol_list':
[(self.data.device_id2, "Copied")]}]
sessions = self.rest.find_snap_vx_sessions(array, source_id)
self.assertEqual(ref_sessions, sessions)
def test_find_snap_vx_sessions_tgt_only(self):
array = self.data.array
source_id = self.data.device_id
ref_sessions = [{'snap_name': 'temp-1',
ref_sessions = [{'generation': '0',
'snap_name': 'temp-1',
'source_vol': self.data.device_id,
'target_vol_list': [self.data.device_id2]}]
'target_vol_list':
[(self.data.device_id2, "Copied")]}]
sessions = self.rest.find_snap_vx_sessions(
array, source_id, tgt_only=True)
self.assertEqual(ref_sessions, sessions)
@ -3206,11 +3246,12 @@ class VMAXProvisionTest(test.TestCase):
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
extra_specs = self.data.extra_specs
ttl = 0
with mock.patch.object(self.provision.rest, 'create_volume_snap'):
self.provision.create_volume_snapvx(
array, source_device_id, snap_name, extra_specs)
self.provision.rest.create_volume_snap.assert_called_once_with(
array, snap_name, source_device_id, extra_specs)
array, snap_name, source_device_id, extra_specs, ttl)
def test_create_volume_replica_create_snap_true(self):
array = self.data.array
@ -3218,6 +3259,8 @@ class VMAXProvisionTest(test.TestCase):
target_device_id = self.data.device_id2
snap_name = self.data.snap_location['snap_name']
extra_specs = self.data.extra_specs
# TTL of 1 hours
ttl = 1
with mock.patch.object(self.provision, 'create_volume_snapvx'):
with mock.patch.object(self.provision.rest, 'modify_volume_snap'):
self.provision.create_volume_replica(
@ -3227,7 +3270,7 @@ class VMAXProvisionTest(test.TestCase):
array, source_device_id, target_device_id, snap_name,
extra_specs, link=True)
self.provision.create_volume_snapvx.assert_called_once_with(
array, source_device_id, snap_name, extra_specs)
array, source_device_id, snap_name, extra_specs, ttl=ttl)
def test_create_volume_replica_create_snap_false(self):
array = self.data.array
@ -3259,7 +3302,7 @@ class VMAXProvisionTest(test.TestCase):
assert_called_once_with(
array, source_device_id, target_device_id,
snap_name, extra_specs,
list_volume_pairs=None, unlink=True))
list_volume_pairs=None, unlink=True, generation=0))
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -3271,7 +3314,7 @@ class VMAXProvisionTest(test.TestCase):
mock_mod.assert_called_once_with(
self.data.array, self.data.device_id, self.data.device_id2,
self.data.snap_location['snap_name'], self.data.extra_specs,
list_volume_pairs=None, unlink=True)
list_volume_pairs=None, unlink=True, generation=0)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -3289,22 +3332,24 @@ class VMAXProvisionTest(test.TestCase):
array = self.data.array
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
generation = 0
with mock.patch.object(self.provision.rest, 'delete_volume_snap'):
self.provision.delete_volume_snap(
array, snap_name, source_device_id)
self.provision.rest.delete_volume_snap.assert_called_once_with(
array, snap_name, source_device_id, False)
array, snap_name, source_device_id, False, generation)
def test_delete_volume_snap_restore(self):
array = self.data.array
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
restored = True
generation = 0
with mock.patch.object(self.provision.rest, 'delete_volume_snap'):
self.provision.delete_volume_snap(
array, snap_name, source_device_id, restored)
self.provision.rest.delete_volume_snap.assert_called_once_with(
array, snap_name, source_device_id, True)
array, snap_name, source_device_id, True, generation)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -3638,7 +3683,8 @@ class VMAXProvisionTest(test.TestCase):
mock_unlink.assert_called_once_with(
self.data.array, "", "", self.data.test_snapshot_snap_name,
self.data.extra_specs, list_volume_pairs=[
(self.data.device_id, VMAXCommonData.device_id2)])
(self.data.device_id, VMAXCommonData.device_id2)],
generation=0)
mock_unlink.reset_mock()
self.provision.delete_volume_snap_check_for_links(
self.data.array, self.data.test_snapshot_snap_name,
@ -3747,11 +3793,13 @@ class VMAXCommonTest(test.TestCase):
def test_delete_snapshot(self):
snap_name = self.data.snap_location['snap_name']
sourcedevice_id = self.data.snap_location['source_id']
generation = 0
with mock.patch.object(self.provision, 'delete_volume_snap'):
self.common.delete_snapshot(self.data.test_snapshot,
self.data.test_volume)
self.provision.delete_volume_snap.assert_called_once_with(
self.data.array, snap_name, [sourcedevice_id])
self.data.array, snap_name, [sourcedevice_id],
restored=False, generation=generation)
def test_delete_snapshot_not_found(self):
with mock.patch.object(self.common, '_parse_snap_info',
@ -4529,7 +4577,7 @@ class VMAXCommonTest(test.TestCase):
array = self.data.array
clone_volume = self.data.test_clone_volume
source_device_id = self.data.device_id
snap_name = "temp-" + source_device_id + clone_volume.id
snap_name = "temp-" + source_device_id + "-snapshot_for_clone"
ref_dict = self.data.provider_location
with mock.patch.object(self.utils, 'get_temp_snap_name',
return_value=snap_name):
@ -4538,7 +4586,7 @@ class VMAXCommonTest(test.TestCase):
self.data.extra_specs)
self.assertEqual(ref_dict, clone_dict)
self.utils.get_temp_snap_name.assert_called_once_with(
('OS-' + clone_volume.id), source_device_id)
source_device_id)
def test_create_replica_failed_cleanup_target(self):
array = self.data.array
@ -4581,6 +4629,7 @@ class VMAXCommonTest(test.TestCase):
snap_name = self.data.failed_resource
clone_name = clone_volume.name
extra_specs = self.data.extra_specs
generation = 0
with mock.patch.object(self.rest, 'get_sync_session',
return_value='session'):
with mock.patch.object(self.provision,
@ -4591,7 +4640,7 @@ class VMAXCommonTest(test.TestCase):
(self.provision.break_replication_relationship.
assert_called_with(
array, target_device_id, source_device_id,
snap_name, extra_specs))
snap_name, extra_specs, generation))
def test_cleanup_target_no_sync(self):
array = self.data.array
@ -4625,19 +4674,22 @@ class VMAXCommonTest(test.TestCase):
volume_name = self.data.test_volume.name
extra_specs = self.data.extra_specs
snap_name = 'temp-1'
generation = '0'
with mock.patch.object(self.rest, 'get_volume_snap',
return_value=snap_name):
self.common._sync_check(array, device_id, volume_name,
extra_specs)
mock_break.assert_called_with(
array, target, device_id, snap_name, extra_specs)
mock_delete.assert_called_with(array, snap_name, device_id)
array, target, device_id, snap_name, extra_specs, generation)
mock_delete.assert_called_with(array, snap_name,
device_id, restored=False,
generation=generation)
# Delete legacy temp snap
mock_delete.reset_mock()
snap_name2 = 'EMC_SMI_12345'
sessions = [{'source_vol': device_id,
'snap_name': snap_name2,
'target_vol_list': []}]
'target_vol_list': [], 'generation': 0}]
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions):
with mock.patch.object(self.rest, 'get_volume_snap',
@ -4645,7 +4697,7 @@ class VMAXCommonTest(test.TestCase):
self.common._sync_check(array, device_id, volume_name,
extra_specs)
mock_delete.assert_called_once_with(
array, snap_name2, device_id)
array, snap_name2, device_id, restored=False, generation=0)
@mock.patch.object(
provision.VMAXProvision,
@ -4661,14 +4713,14 @@ class VMAXCommonTest(test.TestCase):
extra_specs = self.data.extra_specs
snap_name = 'OS-1'
sessions = [{'source_vol': device_id,
'snap_name': snap_name,
'target_vol_list': [target]}]
'snap_name': snap_name, 'generation': 0,
'target_vol_list': [(target, "Copied")]}]
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions):
self.common._sync_check(array, device_id, volume_name,
extra_specs)
mock_break.assert_called_with(
array, target, device_id, snap_name, extra_specs)
array, target, device_id, snap_name, extra_specs, 0)
mock_delete.assert_not_called()
@mock.patch.object(
@ -4685,6 +4737,80 @@ class VMAXCommonTest(test.TestCase):
extra_specs)
mock_break.assert_not_called()
@mock.patch.object(
provision.VMAXProvision,
'delete_volume_snap')
@mock.patch.object(
provision.VMAXProvision,
'break_replication_relationship')
def test_clone_check_cinder_snap(self, mock_break, mock_delete):
array = self.data.array
device_id = self.data.device_id
target = self.data.volume_details[1]['volumeId']
extra_specs = self.data.extra_specs
snap_name = 'OS-1'
sessions = [{'source_vol': device_id,
'snap_name': snap_name, 'generation': 0,
'target_vol_list': [(target, "Copied")]}]
with mock.patch.object(self.rest, 'is_vol_in_rep_session',
return_value=(True, False, None)):
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions):
self.common._clone_check(array, device_id, extra_specs)
mock_delete.assert_not_called()
mock_delete.reset_mock()
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions):
self.common._clone_check(array, device_id, extra_specs)
mock_break.assert_called_with(
array, target, device_id, snap_name, extra_specs, 0)
@mock.patch.object(
provision.VMAXProvision,
'delete_volume_snap')
@mock.patch.object(
provision.VMAXProvision,
'break_replication_relationship')
def test_clone_check_temp_snap(self, mock_break, mock_delete):
array = self.data.array
device_id = self.data.device_id
target = self.data.volume_details[1]['volumeId']
extra_specs = self.data.extra_specs
temp_snap_name = 'temp-' + device_id + '-' + 'snapshot_for_clone'
sessions = [{'source_vol': device_id,
'snap_name': temp_snap_name, 'generation': 0,
'target_vol_list': [(target, "Copied")]}]
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions):
self.common._clone_check(array, device_id, extra_specs)
mock_break.assert_called_with(
array, target, device_id, temp_snap_name, extra_specs, 0)
mock_delete.asset_not_called()
sessions1 = [{'source_vol': device_id,
'snap_name': temp_snap_name, 'generation': 0,
'target_vol_list': [(target, "CopyInProg")]}]
mock_delete.reset_mock()
mock_break.reset_mock()
with mock.patch.object(self.rest, 'is_vol_in_rep_session',
return_value=(False, True, None)):
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=sessions1):
self.common._clone_check(array, device_id, extra_specs)
mock_break.assert_not_called()
mock_delete.asset_not_called()
@mock.patch.object(
provision.VMAXProvision,
'break_replication_relationship')
def test_clone_check_no_sessions(self, mock_break):
array = self.data.array
device_id = self.data.device_id
extra_specs = self.data.extra_specs
with mock.patch.object(self.rest, 'find_snap_vx_sessions',
return_value=None):
self.common._clone_check(array, device_id, extra_specs)
mock_break.assert_not_called()
def test_manage_existing_success(self):
external_ref = {u'source-name': u'00002'}
provider_location = {'device_id': u'00002', 'array': u'000197800123'}

View File

@ -61,6 +61,12 @@ vmax_opts = [
default=200,
help='Use this value to specify '
'number of retries.'),
cfg.IntOpt(utils.VMAX_SNAPVX_UNLINK_LIMIT,
default=3,
help='Use this value to specify '
'the maximum number of unlinks '
'for the temporary snapshots '
'before a clone operation.'),
cfg.BoolOpt('initiator_check',
default=False,
help='Use this value to enable '
@ -149,6 +155,8 @@ class VMAXCommon(object):
"""Get relevent details from configuration file."""
self.interval = self.configuration.safe_get('interval')
self.retries = self.configuration.safe_get('retries')
self.snapvx_unlink_limit = self.configuration.safe_get(
utils.VMAX_SNAPVX_UNLINK_LIMIT)
self.pool_info['backend_name'] = (
self.configuration.safe_get('volume_backend_name'))
mosr = volume_utils.get_max_over_subscription_ratio(
@ -1310,9 +1318,8 @@ class VMAXCommon(object):
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(data=exception_message)
# Check if source is currently a snap target. Wait for sync if true.
self._sync_check(array, source_device_id, source_volume.name,
extra_specs, tgt_only=True)
# Perform any snapvx cleanup if required before creating the clone
self._clone_check(array, source_device_id, extra_specs)
if not is_snapshot:
clone_dict = self._create_replica(
@ -1419,7 +1426,8 @@ class VMAXCommon(object):
return volume_name
array = extra_specs[utils.ARRAY]
# Check if volume is snap source
# Check if the volume being deleted is a
# source or target for copy session
self._sync_check(array, device_id, volume_name, extra_specs)
# Remove from any storage groups and cleanup replication
self._remove_vol_and_cleanup_replication(
@ -1780,8 +1788,7 @@ class VMAXCommon(object):
LOG.info("The target device id is: %(device_id)s.",
{'device_id': target_device_id})
if not snap_name:
snap_name = self.utils.get_temp_snap_name(
clone_name, source_device_id)
snap_name = self.utils.get_temp_snap_name(source_device_id)
create_snap = True
self.provision.create_volume_replica(
array, source_device_id, target_device_id,
@ -1801,7 +1808,7 @@ class VMAXCommon(object):
def _cleanup_target(
self, array, target_device_id, source_device_id,
clone_name, snap_name, extra_specs):
clone_name, snap_name, extra_specs, generation=0):
"""Cleanup target volume on failed clone/ snapshot creation.
:param array: the array serial number
@ -1809,13 +1816,14 @@ class VMAXCommon(object):
:param source_device_id: the source device ID
:param clone_name: the name of the clone volume
:param extra_specs: the extra specifications
:param generation: the generation number of the snapshot
"""
snap_session = self.rest.get_sync_session(
array, source_device_id, snap_name, target_device_id)
array, source_device_id, snap_name, target_device_id, generation)
if snap_session:
self.provision.break_replication_relationship(
array, target_device_id, source_device_id,
snap_name, extra_specs)
snap_name, extra_specs, generation)
self._delete_from_srp(
array, target_device_id, clone_name, extra_specs)
@ -1828,6 +1836,7 @@ class VMAXCommon(object):
:param volume_name: volume name
:param tgt_only: Flag - return only sessions where device is target
:param extra_specs: extra specifications
:param is_clone: Flag to specify if it is a clone operation
"""
get_sessions = False
snapvx_tgt, snapvx_src, __ = self.rest.is_vol_in_rep_session(
@ -1840,17 +1849,21 @@ class VMAXCommon(object):
snap_vx_sessions = self.rest.find_snap_vx_sessions(
array, device_id, tgt_only)
if snap_vx_sessions:
snap_vx_sessions.sort(
key=lambda k: k['generation'], reverse=True)
for session in snap_vx_sessions:
source = session['source_vol']
snap_name = session['snap_name']
targets = session['target_vol_list']
generation = session['generation']
# Break the replication relationship
for target in targets:
# Break the replication relationship
LOG.debug("Unlinking source from target. Source: "
"%(volume)s, Target: %(target)s.",
{'volume': volume_name, 'target': target})
{'volume': source, 'target': target[0]})
self.provision.break_replication_relationship(
array, target, source, snap_name, extra_specs)
array, target[0], source, snap_name,
extra_specs, generation)
# The snapshot name will only have 'temp' (or EMC_SMI for
# legacy volumes) if it is a temporary volume.
# Only then is it a candidate for deletion.
@ -1858,9 +1871,73 @@ class VMAXCommon(object):
@coordination.synchronized("emc-source-{source}")
def do_delete_temp_volume_snap(source):
self.provision.delete_temp_volume_snap(
array, snap_name, source)
array, snap_name, source, generation)
do_delete_temp_volume_snap(source)
def _clone_check(self, array, device_id, extra_specs):
"""Perform any snapvx cleanup before creating clones or snapshots
:param array: the array serial
:param device_id: the device ID of the volume
:param extra_specs: extra specifications
"""
snapvx_tgt, snapvx_src, __ = self.rest.is_vol_in_rep_session(
array, device_id)
if snapvx_src or snapvx_tgt:
snap_vx_sessions = self.rest.find_snap_vx_sessions(
array, device_id)
if snap_vx_sessions:
snap_vx_sessions.sort(
key=lambda k: k['generation'], reverse=True)
count = 0
for session in snap_vx_sessions:
source = session['source_vol']
snap_name = session['snap_name']
targets = session['target_vol_list']
generation = session['generation']
# Only unlink a set number of targets
if count == self.snapvx_unlink_limit:
break
is_temp = False
is_temp = 'temp' in snap_name or 'EMC_SMI' in snap_name
if utils.CLONE_SNAPSHOT_NAME in snap_name:
is_temp = False
for target in targets:
if snapvx_src:
if not is_temp and target[1] == "Copied":
# Break the replication relationship
LOG.debug("Unlinking source from "
"target. Source: %(volume)s, "
"Target: %(target)s.",
{'volume': source,
'target': target[0]})
self.provision.break_replication_relationship(
array, target[0], source, snap_name,
extra_specs, generation)
count = count + 1
elif snapvx_tgt:
# If our device is a target, we need to wait
# and then unlink
LOG.debug("Unlinking source from "
"target. Source: %(volume)s, "
"Target: %(target)s.",
{'volume': source,
'target': target[0]})
self.provision.break_replication_relationship(
array, target[0], source, snap_name,
extra_specs, generation)
# For older styled temp snapshots for clone
# do a delete as well
if is_temp:
@coordination.synchronized(
"emc-source-{source}")
def do_delete_temp_volume_snap(source):
self.provision.delete_temp_volume_snap(
array, snap_name, source, generation)
do_delete_temp_volume_snap(source)
def manage_existing(self, volume, external_ref):
"""Manages an existing VMAX Volume (import to Cinder).
@ -4416,7 +4493,7 @@ class VMAXCommon(object):
LOG.debug("Terminating restore session")
# This may throw an exception if restore_complete is False
self.provision.delete_volume_snap(
array, snap_name, sourcedevice_id, restored=True)
array, snap_name, sourcedevice_id, restored=True, generation=0)
# Revert volume to snapshot is successful if termination was
# successful - possible even if restore_complete was False
# when we checked last.

View File

@ -119,19 +119,20 @@ class VMAXProvision(object):
start_time, time.time())})
def create_volume_snapvx(self, array, source_device_id,
snap_name, extra_specs):
snap_name, extra_specs, ttl=0):
"""Create a snapVx of a volume.
:param array: the array serial number
:param source_device_id: source volume device id
:param snap_name: the snapshot name
:param extra_specs: the extra specifications
:param ttl: time to live in hours, defaults to 0
"""
start_time = time.time()
LOG.debug("Create Snap Vx snapshot of: %(source)s.",
{'source': source_device_id})
self.rest.create_volume_snap(
array, snap_name, source_device_id, extra_specs)
array, snap_name, source_device_id, extra_specs, ttl)
LOG.debug("Create volume snapVx took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(start_time,
time.time())})
@ -150,8 +151,9 @@ class VMAXProvision(object):
"""
start_time = time.time()
if create_snap:
# We are creating a temporary snapshot. Specify a ttl of 1 hour
self.create_volume_snapvx(array, source_device_id,
snap_name, extra_specs)
snap_name, extra_specs, ttl=1)
# Link source to target
self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name,
@ -163,7 +165,7 @@ class VMAXProvision(object):
def break_replication_relationship(
self, array, target_device_id, source_device_id, snap_name,
extra_specs):
extra_specs, generation=0):
"""Unlink a snapshot from its target volume.
:param array: the array serial number
@ -171,17 +173,19 @@ class VMAXProvision(object):
:param target_device_id: target volume device id
:param snap_name: the name for the snap shot
:param extra_specs: extra specifications
:param generation: the generation number of the snapshot
"""
LOG.debug("Break snap vx link relationship between: %(src)s "
"and: %(tgt)s.",
{'src': source_device_id, 'tgt': target_device_id})
self._unlink_volume(array, source_device_id, target_device_id,
snap_name, extra_specs)
snap_name, extra_specs,
list_volume_pairs=None, generation=generation)
def _unlink_volume(
self, array, source_device_id, target_device_id, snap_name,
extra_specs, list_volume_pairs=None):
extra_specs, list_volume_pairs=None, generation=0):
"""Unlink a target volume from its source volume.
:param array: the array serial number
@ -190,6 +194,7 @@ class VMAXProvision(object):
:param snap_name: the snap name
:param extra_specs: extra specifications
:param list_volume_pairs: list of volume pairs, optional
:param generation: the generation number of the snapshot
:return: return code
"""
@ -205,7 +210,8 @@ class VMAXProvision(object):
self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name,
extra_specs, unlink=True,
list_volume_pairs=list_volume_pairs)
list_volume_pairs=list_volume_pairs,
generation=generation)
kwargs['modify_vol_success'] = True
except exception.VolumeBackendAPIException:
pass
@ -224,18 +230,19 @@ class VMAXProvision(object):
return rc
def delete_volume_snap(self, array, snap_name,
source_device_id, restored=False):
source_device_id, restored=False, generation=0):
"""Delete a snapVx snapshot of a volume.
:param array: the array serial number
:param snap_name: the snapshot name
:param source_device_id: the source device id
:param restored: Flag to indicate if restored session is being deleted
:param generation: the snapshot generation number
"""
LOG.debug("Delete SnapVx: %(snap_name)s for volume %(vol)s.",
{'vol': source_device_id, 'snap_name': snap_name})
self.rest.delete_volume_snap(
array, snap_name, source_device_id, restored)
array, snap_name, source_device_id, restored, generation)
def is_restore_complete(self, array, source_device_id,
snap_name, extra_specs):
@ -303,7 +310,8 @@ class VMAXProvision(object):
restored = True
return restored
def delete_temp_volume_snap(self, array, snap_name, source_device_id):
def delete_temp_volume_snap(self, array, snap_name,
source_device_id, generation=0):
"""Delete the temporary snapshot created for clone operations.
There can be instances where the source and target both attempt to
@ -312,19 +320,22 @@ class VMAXProvision(object):
:param array: the array serial number
:param snap_name: the snapshot name
:param source_device_id: the source device id
:param generation: the generation number for the snapshot
"""
@coordination.synchronized("emc-snapvx-{snapvx_name}")
def do_delete_temp_snap(snapvx_name):
# Ensure snap has not been recently deleted
if self.rest.get_volume_snap(
array, source_device_id, snapvx_name):
self.delete_volume_snap(array, snapvx_name, source_device_id)
array, source_device_id, snapvx_name, generation):
self.delete_volume_snap(
array, snapvx_name, source_device_id,
restored=False, generation=generation)
do_delete_temp_snap(snap_name)
def delete_volume_snap_check_for_links(self, array, snap_name,
source_devices, extra_specs):
def delete_volume_snap_check_for_links(
self, array, snap_name, source_devices, extra_specs, generation=0):
"""Check if a snap has any links before deletion.
If a snapshot has any links, break the replication relationship
@ -333,6 +344,7 @@ class VMAXProvision(object):
:param snap_name: the snapshot name
:param source_devices: the source device ids
:param extra_specs: the extra specifications
:param generation: the generation number for the snapshot
"""
list_device_pairs = []
if not isinstance(source_devices, list):
@ -342,7 +354,7 @@ class VMAXProvision(object):
"for volume %(vol)s.",
{'vol': source_device, 'snap_name': snap_name})
linked_list = self.rest.get_snap_linked_device_list(
array, source_device, snap_name)
array, source_device, snap_name, generation)
if len(linked_list) == 1:
target_device = linked_list[0]['targetDevice']
list_device_pairs.append((source_device, target_device))
@ -352,11 +364,13 @@ class VMAXProvision(object):
# we must unlink each target individually
target_device = link['targetDevice']
self._unlink_volume(array, source_device, target_device,
snap_name, extra_specs)
snap_name, extra_specs, generation)
if list_device_pairs:
self._unlink_volume(array, "", "", snap_name, extra_specs,
list_volume_pairs=list_device_pairs)
self.delete_volume_snap(array, snap_name, source_devices)
list_volume_pairs=list_device_pairs,
generation=generation)
self.delete_volume_snap(array, snap_name, source_devices,
restored=False, generation=generation)
def extend_volume(self, array, device_id, new_size, extra_specs,
rdf_group=None):

View File

@ -0,0 +1,6 @@
---
features:
- Support for new configuration option - vmax_snapvx_unlink_limit
for specifying the maximum number of unlinks which will be performed
before a clone operation. Default value is 3

View File

@ -1612,17 +1612,22 @@ class VMAXRest(object):
"for array %(array)s", {'array': array})
return snap_capability
def create_volume_snap(self, array, snap_name, device_id, extra_specs):
def create_volume_snap(self, array, snap_name, device_id,
extra_specs, ttl=0):
"""Create a snapVx snapshot of a volume.
:param array: the array serial number
:param snap_name: the name of the snapshot
:param device_id: the source device id
:param extra_specs: the extra specifications
:param ttl: time to live in hours, defaults to 0
"""
payload = {"deviceNameListSource": [{"name": device_id}],
"bothSides": 'false', "star": 'false',
"force": 'false'}
if int(ttl) > 0:
payload['timeToLive'] = ttl
payload['timeInHours'] = 'true'
resource_type = 'snapshot/%(snap)s' % {'snap': snap_name}
status_code, job = self.create_resource(
array, REPLICATION, resource_type,
@ -1633,7 +1638,7 @@ class VMAXRest(object):
def modify_volume_snap(self, array, source_id, target_id, snap_name,
extra_specs, link=False, unlink=False,
rename=False, new_snap_name=None, restore=False,
list_volume_pairs=None):
list_volume_pairs=None, generation=0):
"""Modify a snapvx snapshot
:param array: the array serial number
@ -1647,6 +1652,7 @@ class VMAXRest(object):
:param new_snap_name: Optional new snapshot name
:param restore: Flag to indicate action = Restore
:param list_volume_pairs: list of volume pairs to link, optional
:param generation: the generation number of the snapshot
"""
action, operation, payload = '', '', {}
if link:
@ -1680,7 +1686,8 @@ class VMAXRest(object):
"copy": 'true', "action": action,
"star": 'false', "force": 'false',
"exact": 'false', "remote": 'false',
"symforce": 'false', "nocopy": 'false'}
"symforce": 'false', "nocopy": 'false',
"generation": generation}
elif action == "Rename":
operation = 'Rename snapVx snapshot'
@ -1695,20 +1702,22 @@ class VMAXRest(object):
self.wait_for_job(operation, status_code, job, extra_specs)
def delete_volume_snap(self, array, snap_name,
source_device_ids, restored=False):
source_device_ids, restored=False, generation=0):
"""Delete the snapshot of a volume or volumes.
:param array: the array serial number
:param snap_name: the name of the snapshot
:param source_device_ids: the source device ids
:param restored: Flag to indicate terminate restore session
:param generation: the generation number of the snapshot
"""
device_list = []
if not isinstance(source_device_ids, list):
source_device_ids = [source_device_ids]
for dev in source_device_ids:
device_list.append({"name": dev})
payload = {"deviceNameListSource": device_list}
payload = {"deviceNameListSource": device_list,
"generation": int(generation)}
if restored:
payload.update({"restore": True})
return self.delete_resource(
@ -1727,12 +1736,13 @@ class VMAXRest(object):
return self.get_resource(array, REPLICATION, 'volume',
resource_name, private='/private')
def get_volume_snap(self, array, device_id, snap_name):
def get_volume_snap(self, array, device_id, snap_name, generation=0):
"""Given a volume snap info, retrieve the snapVx object.
:param array: the array serial number
:param device_id: the source volume device id
:param snap_name: the name of the snapshot
:param generation: the generation number of the snapshot
:returns: snapshot dict, or None
"""
snapshot = None
@ -1740,9 +1750,11 @@ class VMAXRest(object):
if snap_info:
if (snap_info.get('snapshotSrcs') and
bool(snap_info['snapshotSrcs'])):
for snap in snap_info['snapshotSrcs']:
if snap['snapshotName'] == snap_name:
snapshot = snap
for snap in snap_info['snapshotSrcs']:
if snap['snapshotName'] == snap_name:
if snap['generation'] == generation:
snapshot = snap
break
return snapshot
def get_volume_snapshot_list(self, array, source_device_id):
@ -1846,21 +1858,23 @@ class VMAXRest(object):
return defined
def get_sync_session(self, array, source_device_id, snap_name,
target_device_id):
target_device_id, generation=0):
"""Get a particular sync session.
:param array: the array serial number
:param source_device_id: source device id
:param snap_name: the snapshot name
:param target_device_id: the target device id
:param generation: the generation number of the snapshot
:returns: sync session -- dict, or None
"""
session = None
linked_device_list = self.get_snap_linked_device_list(
array, source_device_id, snap_name)
array, source_device_id, snap_name, generation)
for target in linked_device_list:
if target_device_id == target['targetDevice']:
session = target
break
return session
def _find_snap_vx_source_sessions(self, array, source_device_id):
@ -1875,24 +1889,67 @@ class VMAXRest(object):
for snapshot in snapshots:
if bool(snapshot['linkedDevices']):
link_info = {'linked_vols': snapshot['linkedDevices'],
'snap_name': snapshot['snapshotName']}
'snap_name': snapshot['snapshotName'],
'generation': snapshot['generation']}
snap_dict_list.append(link_info)
return snap_dict_list
def get_snap_linked_device_list(self, array, source_device_id, snap_name):
def get_snap_linked_device_list(self, array, source_device_id,
snap_name, generation=0, state=None):
"""Get the list of linked devices for a particular snapVx snapshot.
:param array: the array serial number
:param source_device_id: source device id
:param snap_name: the snapshot name
:returns: linked_device_list
:param generation: the generation number of the snapshot
:param state: filter for state of the link
:returns: linked_device_list or empty list
"""
snap_dict_list = None
linked_device_list = []
snap_list = self._find_snap_vx_source_sessions(array, source_device_id)
snap_dict_list = self._get_snap_linked_device_dict_list(
array, source_device_id, snap_name, state=state)
for snap_dict in snap_dict_list:
if generation == snap_dict['generation']:
linked_device_list = snap_dict['linked_vols']
break
return linked_device_list
def _get_snap_linked_device_dict_list(
self, array, source_device_id, snap_name, state=None):
"""Get list of linked devices for all generations for a snapVx snapshot
:param array: the array serial number
:param source_device_id: source device id
:param snap_name: the snapshot name
:param state: filter for state of the link
:return: list of dict of generations with linked devices
"""
snap_dict_list = []
snap_list = self._find_snap_vx_source_sessions(
array, source_device_id)
snap_state = None
for snap in snap_list:
if snap['snap_name'] == snap_name:
linked_device_list = snap['linked_vols']
return linked_device_list
for linked_vol in snap['linked_vols']:
snap_state = linked_vol.get('state', None)
# If state is None or
# both snap_state and state are not None and are equal
if not state or (snap_state and state
and snap_state == state):
generation = snap['generation']
found = False
for snap_dict in snap_dict_list:
if generation == snap_dict['generation']:
snap_dict['linked_vols'].append(
linked_vol)
found = True
break
if not found:
snap_dict_list.append(
{'generation': generation,
'linked_vols': [linked_vol]})
return snap_dict_list
def find_snap_vx_sessions(self, array, device_id, tgt_only=False):
"""Find all snapVX sessions for a device (source and target).
@ -1915,25 +1972,31 @@ class VMAXRest(object):
src_list = session['srcSnapshotGenInfo']
for src in src_list:
snap_name = src['snapshotHeader']['snapshotName']
target_list, target_dict = [], {}
generation = src['snapshotHeader']['generation']
target_list, target_dict_list = [], []
if src.get('lnkSnapshotGenInfo'):
target_dict = src['lnkSnapshotGenInfo']
for tgt in target_dict:
target_list.append(tgt['targetDevice'])
target_dict_list = src['lnkSnapshotGenInfo']
for tgt in target_dict_list:
target_tup = tgt['targetDevice'], tgt['state']
target_list.append(target_tup)
link_info = {'target_vol_list': target_list,
'snap_name': snap_name,
'source_vol': device_id}
'source_vol': device_id,
'generation': generation}
snap_dict_list.append(link_info)
if is_snap_tgt:
for session in sessions:
if session.get('tgtSrcSnapshotGenInfo'):
tgt = session['tgtSrcSnapshotGenInfo']
snap_name = tgt['snapshotName']
target_list = [tgt['targetDevice']]
target_tup = tgt['targetDevice'], tgt['state']
target_list = [target_tup]
source_vol = tgt['sourceDevice']
generation = tgt['generation']
link_info = {'target_vol_list': target_list,
'snap_name': snap_name,
'source_vol': source_vol}
'source_vol': source_vol,
'generation': generation}
snap_dict_list.append(link_info)
return snap_dict_list

View File

@ -72,6 +72,7 @@ RDF_ACTIVEACTIVE = 'activeactive'
RDF_ACTIVEBIAS = 'activebias'
METROBIAS = 'metro_bias'
DEFAULT_PORT = 8443
CLONE_SNAPSHOT_NAME = "snapshot_for_clone"
# Multiattach constants
IS_MULTIATTACH = 'multiattach'
@ -90,6 +91,7 @@ VMAX_WORKLOAD = 'vmax_workload'
VMAX_SRP = 'vmax_srp'
VMAX_SERVICE_LEVEL = 'vmax_service_level'
VMAX_PORT_GROUPS = 'vmax_port_groups'
VMAX_SNAPVX_UNLINK_LIMIT = 'vmax_snapvx_unlink_limit'
class VMAXUtils(object):
@ -307,16 +309,15 @@ class VMAXUtils(object):
max_over_sub_ratio = 20.0
return max_over_sub_ratio
def get_temp_snap_name(self, clone_name, source_device_id):
"""Construct a temporary snapshot name for clone operation.
def get_temp_snap_name(self, source_device_id):
"""Construct a temporary snapshot name for clone operation
:param clone_name: the name of the clone
:param source_device_id: the source device id
:returns: snap_name
:return: snap_name
"""
trunc_clone = self.truncate_string(clone_name, 10)
snap_name = ("temp-%(device)s-%(clone)s"
% {'device': source_device_id, 'clone': trunc_clone})
snap_name = ("temp-%(device)s-%(snap_name)s"
% {'device': source_device_id,
'snap_name': CLONE_SNAPSHOT_NAME})
return snap_name
@staticmethod