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:
parent
92fbe37087
commit
40c563a841
|
@ -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'}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue