Merge "PowerMax Driver - Replacing generations with snap_ids"

This commit is contained in:
Zuul 2020-08-24 19:43:32 +00:00 committed by Gerrit Code Review
commit 2eba48730e
13 changed files with 542 additions and 309 deletions

View File

@ -93,6 +93,8 @@ class PowerMaxData(object):
next_gen_ucode = 5978
gvg_group_id = 'test-gvg'
sg_tags = 'production,test'
snap_id = 118749976833
snap_id_2 = 118749976833
# connector info
wwpn1 = '123456789012345'
@ -870,7 +872,7 @@ class PowerMaxData(object):
{'snapshotHeader': {
'snapshotName': 'temp-1',
'device': device_id,
'generation': '0'},
'snapid': snap_id},
'lnkSnapshotGenInfo': [
{'targetDevice': device_id2,
'state': 'Copied'}]}]},
@ -878,7 +880,7 @@ class PowerMaxData(object):
'snapshotName': 'temp-1',
'targetDevice': device_id2,
'sourceDevice': device_id,
'generation': '0',
'snapid': snap_id_2,
'state': 'Copied'}}],
'snapVXSrc': 'true',
'snapVXTgt': 'true'},
@ -906,14 +908,15 @@ class PowerMaxData(object):
# replication
volume_snap_vx = {'snapshotLnks': [],
'snapshotSrcs': [
{'generation': 0,
{'snap_id': snap_id,
'linkedDevices': [
{'targetDevice': device_id2,
'percentageCopied': 100,
'state': 'Copied',
'copy': True,
'defined': True,
'linked': True}],
'linked': True,
'snap_id': snap_id}],
'snapshotName': test_snapshot_snap_name,
'state': 'Established'}]}
capabilities = {'symmetrixCapability': [{'rdfCapable': True,
@ -1068,7 +1071,8 @@ class PowerMaxData(object):
{'snapshotHeader': {
'timestamp': 1512763278000, 'expired': False,
'secured': False, 'snapshotName': 'testSnap1',
'device': '00001', 'generation': 0, 'timeToLive': 0
'device': '00001', 'snapid': snap_id,
'timeToLive': 0, 'generation': 0
}}]}]}}]
priv_vol_func_response_multi = [
@ -1094,7 +1098,8 @@ class PowerMaxData(object):
{'snapshotHeader': {
'timestamp': 1512763278000, 'expired': False,
'secured': False, 'snapshotName': 'testSnap1',
'device': '00001', 'generation': 0, 'timeToLive': 0
'device': '00001', 'snapid': snap_id,
'timeToLive': 0, 'generation': 0
}}]}]}},
{'volumeHeader': {
'private': False, 'capGB': 200.0, 'capMB': 204800.0,
@ -1118,7 +1123,8 @@ class PowerMaxData(object):
{'snapshotHeader': {
'timestamp': 1512763278000, 'expired': False,
'secured': False, 'snapshotName': 'testSnap2',
'device': '00002', 'generation': 0, 'timeToLive': 0
'device': '00002', 'snapid': snap_id,
'timeToLive': 0, 'generation': 0
}}]}]}},
{'volumeHeader': {
'private': False, 'capGB': 300.0, 'capMB': 307200.0,
@ -1142,7 +1148,8 @@ class PowerMaxData(object):
{'snapshotHeader': {
'timestamp': 1512763278000, 'expired': False,
'secured': False, 'snapshotName': 'testSnap3',
'device': '00003', 'generation': 0, 'timeToLive': 0
'device': '00003', 'snapid': snap_id,
'timeToLive': 0, 'generation': 0
}}]}]}},
{'volumeHeader': {
'private': False, 'capGB': 400.0, 'capMB': 409600.0,
@ -1166,7 +1173,8 @@ class PowerMaxData(object):
{'snapshotHeader': {
'timestamp': 1512763278000, 'expired': False,
'secured': False, 'snapshotName': 'testSnap4',
'device': '00004', 'generation': 0, 'timeToLive': 0
'device': '00004', 'snapid': snap_id,
'timeToLive': 0, 'generation': 0
}}]}]}}]
priv_vol_func_response_multi_invalid = [
@ -1340,19 +1348,21 @@ class PowerMaxData(object):
'RestServerPort': '8443',
'RestUserName': 'test',
'RestPassword': 'test',
'SSLVerify': '/path/to/cert'},
'SSLVerify': '/path/to/cert',
'SerialNumber': array},
{'RestServerIp': '10.10.10.12',
'RestServerPort': '8443',
'RestUserName': 'test',
'RestPassword': 'test',
'SSLVerify': 'True'}]
'SSLVerify': 'True',
'SerialNumber': array}]
snapshot_src_details = {'snapshotSrcs': [{
'snapshotName': 'temp-000AA-snapshot_for_clone',
'generation': 0, 'state': 'Established', 'expired': False,
'snap_id': snap_id, 'state': 'Established', 'expired': False,
'linkedDevices': [{'targetDevice': device_id2, 'state': 'Copied',
'copy': True}]},
{'snapshotName': 'temp-000AA-snapshot_for_clone', 'generation': 1,
{'snapshotName': 'temp-000AA-snapshot_for_clone', 'snap_id': snap_id_2,
'state': 'Established', 'expired': False,
'linkedDevices': [{'targetDevice': device_id3, 'state': 'Copied',
'copy': True}]}],
@ -1363,24 +1373,24 @@ class PowerMaxData(object):
snap_tgt_vol_details = {"timeFinderInfo": {"snapVXSession": [{
"tgtSrcSnapshotGenInfo": {
"generation": 6, "expired": True,
"snapid": snap_id, "expired": True,
"snapshotName": "temp-000AA-snapshot_for_clone"}}]}}
snap_tgt_session = {
'generation': 0, 'expired': False, 'copy_mode': False,
'snapid': snap_id, 'expired': False, 'copy_mode': False,
'snap_name': 'temp-000AA-snapshot_for_clone', 'state': 'Copied',
'source_vol_id': device_id, 'target_vol_id': device_id2}
snap_tgt_session_cm_enabled = {
'generation': 0, 'expired': False, 'copy_mode': True,
'snapid': snap_id, 'expired': False, 'copy_mode': True,
'snap_name': 'temp-000AA-snapshot_for_clone', 'state': 'Copied',
'source_vol_id': device_id, 'target_vol_id': device_id2}
snap_src_sessions = [
{'generation': 0, 'expired': False, 'copy_mode': False,
{'snapid': snap_id, 'expired': False, 'copy_mode': False,
'snap_name': 'temp-000AA-snapshot_for_clone', 'state': 'Copied',
'source_vol_id': device_id, 'target_vol_id': device_id3},
{'generation': 1, 'expired': False, 'copy_mode': False,
{'snapid': snap_id_2, 'expired': False, 'copy_mode': False,
'snap_name': 'temp-000AA-snapshot_for_clone', 'state': 'Copied',
'source_vol_id': device_id, 'target_vol_id': device_id4}]
@ -1449,7 +1459,7 @@ class PowerMaxData(object):
priv_snap_response = {
'deviceName': snap_device_label, 'snapshotLnks': [],
'snapshotSrcs': [
{'generation': 0,
{'snap_id': snap_id,
'linkedDevices': [
{'targetDevice': device_id2, 'percentageCopied': 100,
'state': 'Copied', 'copy': True, 'defined': True,
@ -1620,3 +1630,7 @@ class PowerMaxData(object):
legacy_mvs = [legacy_mv1, legacy_mv2]
legacy_not_shared_mv = 'OS-myhostA-SRP_1-Diamond-NONE-MV'
legacy_not_shared_sg = 'OS-myhostA-SRP_1-Diamond-NONE-SG'
snapshot_metadata = {'SnapshotLabel': test_snapshot_snap_name,
'SourceDeviceID': device_id,
'SourceDeviceLabel': device_label,
'SnapIdList': [snap_id]}

View File

@ -229,7 +229,7 @@ class FakeRequestsSession(object):
def _replication_sg(self, url):
return_object = None
if 'generation' in url:
if 'snapid' in url:
return_object = self.data.group_snap_vx
elif 'rdf_group' in url:
for sg in self.data.sg_rdf_details:

View File

@ -64,6 +64,7 @@ class PowerMaxCommonTest(test.TestCase):
self.utils = self.common.utils
self.utils.get_volumetype_extra_specs = (
mock.Mock(return_value=self.data.vol_type_extra_specs))
self.rest.is_snap_id = True
@mock.patch.object(rest.PowerMaxRest, 'get_array_ucode_version',
return_value=tpd.PowerMaxData.next_gen_ucode)
@ -396,21 +397,25 @@ class PowerMaxCommonTest(test.TestCase):
snapshot, self.data.test_volume)
self.assertEqual(ref_model_update, model_update)
def test_delete_snapshot(self):
@mock.patch.object(
common.PowerMaxCommon, '_parse_snap_info',
return_value=(tpd.PowerMaxData.device_id,
tpd.PowerMaxData.snap_location['snap_name'],
[tpd.PowerMaxData.snap_id]))
def test_delete_snapshot(self, mock_parse):
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') as mock_delete_snap:
self.common.delete_snapshot(
self.data.test_snapshot, self.data.test_volume)
mock_delete_snap.assert_called_once_with(
self.data.array, snap_name, [sourcedevice_id],
restored=False, generation=generation)
self.data.snap_id, restored=False)
def test_delete_snapshot_not_found(self):
with mock.patch.object(self.common, '_parse_snap_info',
return_value=(None, 'Something')):
return_value=(None, 'Something', None)):
with mock.patch.object(
self.provision, 'delete_volume_snap') as mock_delete_snap:
self.common.delete_snapshot(self.data.test_snapshot,
@ -1144,7 +1149,7 @@ class PowerMaxCommonTest(test.TestCase):
def test_parse_snap_info_found(self):
ref_device_id = self.data.device_id
ref_snap_name = self.data.snap_location['snap_name']
sourcedevice_id, foundsnap_name = self.common._parse_snap_info(
sourcedevice_id, foundsnap_name, __ = self.common._parse_snap_info(
self.data.array, self.data.test_snapshot)
self.assertEqual(ref_device_id, sourcedevice_id)
self.assertEqual(ref_snap_name, foundsnap_name)
@ -1153,22 +1158,22 @@ class PowerMaxCommonTest(test.TestCase):
ref_snap_name = None
with mock.patch.object(self.rest, 'get_volume_snap',
return_value=None):
__, foundsnap_name = self.common._parse_snap_info(
__, foundsnap_name, __ = self.common._parse_snap_info(
self.data.array, self.data.test_snapshot)
self.assertIsNone(ref_snap_name, foundsnap_name)
def test_parse_snap_info_exception(self):
with mock.patch.object(
self.rest, 'get_volume_snap',
self.rest, 'get_volume_snaps',
side_effect=exception.VolumeBackendAPIException):
__, foundsnap_name = self.common._parse_snap_info(
__, foundsnap_name, __ = self.common._parse_snap_info(
self.data.array, self.data.test_snapshot)
self.assertIsNone(foundsnap_name)
def test_parse_snap_info_provider_location_not_string(self):
snapshot = fake_snapshot.fake_snapshot_obj(
context='ctxt', provider_loaction={'not': 'string'})
sourcedevice_id, foundsnap_name = self.common._parse_snap_info(
sourcedevice_id, foundsnap_name, __ = self.common._parse_snap_info(
self.data.array, snapshot)
self.assertIsNone(foundsnap_name)
@ -1747,10 +1752,12 @@ class PowerMaxCommonTest(test.TestCase):
clone_name, snap_name, self.data.extra_specs,
target_volume=clone_volume)
@mock.patch.object(rest.PowerMaxRest, 'get_snap_id',
return_value=tpd.PowerMaxData.snap_id)
@mock.patch.object(
masking.PowerMaxMasking,
'remove_and_reset_members')
def test_cleanup_target_sync_present(self, mock_remove):
def test_cleanup_target_sync_present(self, mock_remove, mock_snaps):
array = self.data.array
clone_volume = self.data.test_clone_volume
source_device_id = self.data.device_id
@ -1758,7 +1765,6 @@ class PowerMaxCommonTest(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(
@ -1768,10 +1774,13 @@ class PowerMaxCommonTest(test.TestCase):
clone_name, snap_name, extra_specs)
mock_break.assert_called_with(
array, target_device_id, source_device_id,
snap_name, extra_specs, generation)
snap_name, extra_specs, self.data.snap_id)
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snaps',
return_value=[{'snap_name': 'snap_name',
'snap_id': tpd.PowerMaxData.snap_id}])
@mock.patch.object(masking.PowerMaxMasking, 'remove_volume_from_sg')
def test_cleanup_target_no_sync(self, mock_remove):
def test_cleanup_target_no_sync(self, mock_remove, mock_snaps):
array = self.data.array
clone_volume = self.data.test_clone_volume
source_device_id = self.data.device_id
@ -2646,8 +2655,9 @@ class PowerMaxCommonTest(test.TestCase):
self.data.test_snapshot)
self.assertEqual(2, size)
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap',
return_value={'snap_name': 'snap_name'})
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snaps',
return_value=[{'snap_name': 'snap_name',
'snap_id': tpd.PowerMaxData.snap_id}])
@mock.patch.object(
common.PowerMaxCommon, 'get_snapshot_metadata',
return_value={'snap-meta-key-1': 'snap-meta-value-1',
@ -2732,29 +2742,35 @@ class PowerMaxCommonTest(test.TestCase):
self.common.unmanage_snapshot,
self.data.test_snapshot_manage)
@mock.patch.object(
common.PowerMaxCommon, '_parse_snap_info', return_value=(
tpd.PowerMaxData.device_id,
tpd.PowerMaxData.snap_location['snap_name'],
[tpd.PowerMaxData.snap_id]))
@mock.patch.object(provision.PowerMaxProvision, 'delete_volume_snap')
@mock.patch.object(provision.PowerMaxProvision, 'is_restore_complete',
return_value=True)
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
@mock.patch.object(provision.PowerMaxProvision, 'revert_volume_snapshot')
def test_revert_to_snapshot(self, mock_revert, mock_clone,
mock_complete, mock_delete):
mock_complete, mock_delete, mock_parse):
volume = self.data.test_volume
snapshot = self.data.test_snapshot
array = self.data.array
device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
snap_id = self.data.snap_id
extra_specs = deepcopy(self.data.extra_specs_intervals_set)
extra_specs['storagetype:portgroupname'] = (
self.data.port_group_name_f)
self.common.revert_to_snapshot(volume, snapshot)
mock_revert.assert_called_once_with(
array, device_id, snap_name, extra_specs)
array, device_id, snap_name, snap_id, extra_specs)
mock_clone.assert_called_once_with(array, device_id, extra_specs)
mock_complete.assert_called_once_with(array, device_id,
snap_name, extra_specs)
snap_name, snap_id, extra_specs)
mock_delete.assert_called_once_with(array, snap_name, device_id,
restored=True, generation=0)
self.data.snap_id, restored=True)
@mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled',
return_value=True)
@ -2837,9 +2853,9 @@ class PowerMaxCommonTest(test.TestCase):
'reason_not_safe': None, 'cinder_id': None,
'extra_info': {
'generation': 0, 'secured': False, 'timeToLive': 'N/A',
'timestamp': mock.ANY},
'timestamp': mock.ANY, 'snap_id': self.data.snap_id},
'source_reference': {'source-id': '00001'}}]
self.assertEqual(snap_list, expected_response)
self.assertEqual(expected_response, snap_list)
def test_get_manageable_snapshots_filters_set(self):
marker, limit, offset = 'testSnap2', 2, 1
@ -2853,14 +2869,16 @@ class PowerMaxCommonTest(test.TestCase):
{'reference': {'source-name': 'testSnap3'},
'safe_to_manage': True, 'size': 300, 'reason_not_safe': None,
'cinder_id': None, 'extra_info': {
'generation': 0, 'secured': False, 'timeToLive': 'N/A',
'timestamp': mock.ANY},
'snap_id': self.data.snap_id, 'secured': False,
'timeToLive': 'N/A', 'timestamp': mock.ANY,
'generation': 0},
'source_reference': {'source-id': '00003'}},
{'reference': {'source-name': 'testSnap4'},
'safe_to_manage': True, 'size': 400, 'reason_not_safe': None,
'cinder_id': None, 'extra_info': {
'generation': 0, 'secured': False, 'timeToLive': 'N/A',
'timestamp': mock.ANY},
'snap_id': self.data.snap_id, 'secured': False,
'timeToLive': 'N/A', 'timestamp': mock.ANY,
'generation': 0},
'source_reference': {'source-id': '00004'}}]
self.assertEqual(vols_lists, expected_response)
@ -3203,14 +3221,14 @@ class PowerMaxCommonTest(test.TestCase):
session = self.data.snap_tgt_session_cm_enabled
snap_name = session['snap_name']
source = session['source_vol_id']
generation = session['generation']
snap_id = session['snapid']
target = session['target_vol_id']
self.common._unlink_targets_and_delete_temp_snapvx(
session, array, extra_specs)
mck_break.assert_called_with(array, target, source, snap_name,
extra_specs, generation, True)
mck_del.assert_called_once_with(array, snap_name, source, generation)
extra_specs, snap_id, True)
mck_del.assert_called_once_with(array, snap_name, source, snap_id)
mck_break.reset_mock()
mck_del.reset_mock()
@ -3220,7 +3238,7 @@ class PowerMaxCommonTest(test.TestCase):
self.common._unlink_targets_and_delete_temp_snapvx(
session, array, extra_specs)
mck_break.assert_called_with(array, target, source, snap_name,
extra_specs, generation, False)
extra_specs, snap_id, False)
mck_del.assert_not_called()
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
@ -3385,7 +3403,9 @@ class PowerMaxCommonTest(test.TestCase):
snap_name = self.data.test_snapshot_snap_name
ref_metadata = {'SnapshotLabel': snap_name,
'SourceDeviceID': device_id,
'SourceDeviceLabel': device_label}
'SourceDeviceLabel': device_label,
'SnapIdList': six.text_type(self.data.snap_id),
'is_snap_id': True}
act_metadata = self.common.get_snapshot_metadata(
array, device_id, snap_name)

View File

@ -151,7 +151,7 @@ class PowerMaxVolumeMetadataDebugTest(test.TestCase):
def test_capture_snapshot_info(self, mock_uvim):
self.volume_metadata.capture_snapshot_info(
self.data.test_volume, self.data.extra_specs, 'createSnapshot',
'ss-test-vol')
self.data.snapshot_metadata)
mock_uvim.assert_called_once()
@mock.patch.object(

View File

@ -165,11 +165,11 @@ class PowerMaxProvisionTest(test.TestCase):
self.provision, '_unlink_volume') as mock_unlink:
self.provision.unlink_snapvx_tgt_volume(
array, target_device_id, source_device_id, snap_name,
extra_specs, generation=6, loop=True)
extra_specs, self.data.snap_id, loop=True)
mock_unlink.assert_called_once_with(
array, source_device_id, target_device_id,
snap_name, extra_specs, list_volume_pairs=None,
generation=6, loop=True)
snap_name, extra_specs, snap_id=self.data.snap_id,
list_volume_pairs=None, loop=True)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -177,21 +177,22 @@ class PowerMaxProvisionTest(test.TestCase):
with mock.patch.object(self.rest, 'modify_volume_snap') as mock_mod:
self.provision._unlink_volume(
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)
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, generation=0)
snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True)
mock_mod.reset_mock()
self.provision._unlink_volume(
self.data.array, self.data.device_id, self.data.device_id2,
self.data.snap_location['snap_name'], self.data.extra_specs,
loop=False)
snap_id=self.data.snap_id, loop=False)
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, generation=0)
snap_id=self.data.snap_id, list_volume_pairs=None, unlink=True)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -202,31 +203,33 @@ class PowerMaxProvisionTest(test.TestCase):
) as mock_mod:
self.provision._unlink_volume(
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,
self.data.snap_id)
self.assertEqual(2, mock_mod.call_count)
def test_delete_volume_snap(self):
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)
array, snap_name, source_device_id, snap_id=self.data.snap_id)
self.provision.rest.delete_volume_snap.assert_called_once_with(
array, snap_name, source_device_id, False, generation)
array, snap_name, source_device_id, snap_id=self.data.snap_id,
restored=False)
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)
array, snap_name, source_device_id, snap_id=self.data.snap_id,
restored=restored)
self.provision.rest.delete_volume_snap.assert_called_once_with(
array, snap_name, source_device_id, True, generation)
array, snap_name, source_device_id, snap_id=self.data.snap_id,
restored=True)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -234,37 +237,40 @@ class PowerMaxProvisionTest(test.TestCase):
array = self.data.array
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
snap_id = self.data.snap_id
extra_specs = self.data.extra_specs
with mock.patch.object(
self.provision, '_is_restore_complete',
return_value=True):
isrestored = self.provision.is_restore_complete(
array, source_device_id, snap_name, extra_specs)
array, source_device_id, snap_name, snap_id, extra_specs)
self.assertTrue(isrestored)
with mock.patch.object(
self.provision, '_is_restore_complete',
side_effect=exception.CinderException):
self.assertRaises(exception.VolumeBackendAPIException,
self.provision.is_restore_complete,
array, source_device_id, snap_name, extra_specs)
array, source_device_id, snap_name, snap_id,
extra_specs)
def test_is_restore_complete(self):
array = self.data.array
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
snap_id = self.data.snap_id
snap_details = {
'linkedDevices':
[{'targetDevice': source_device_id, 'state': 'Restored'}]}
with mock.patch.object(self.provision.rest,
'get_volume_snap', return_value=snap_details):
isrestored = self.provision._is_restore_complete(
array, source_device_id, snap_name)
array, source_device_id, snap_name, snap_id)
self.assertTrue(isrestored)
snap_details['linkedDevices'][0]['state'] = 'Restoring'
with mock.patch.object(self.provision.rest,
'get_volume_snap', return_value=snap_details):
isrestored = self.provision._is_restore_complete(
array, source_device_id, snap_name)
array, source_device_id, snap_name, snap_id)
self.assertFalse(isrestored)
def test_revert_volume_snapshot(self):
@ -272,13 +278,14 @@ class PowerMaxProvisionTest(test.TestCase):
source_device_id = self.data.device_id
snap_name = self.data.snap_location['snap_name']
extra_specs = self.data.extra_specs
snap_id = self.data.snap_id
with mock.patch.object(
self.provision.rest, 'modify_volume_snap', return_value=None):
self.provision.revert_volume_snapshot(
array, source_device_id, snap_name, extra_specs)
array, source_device_id, snap_name, snap_id, extra_specs)
self.provision.rest.modify_volume_snap.assert_called_once_with(
array, source_device_id, "", snap_name,
extra_specs, restore=True)
extra_specs, snap_id=snap_id, restore=True)
def test_extend_volume(self):
array = self.data.array
@ -459,9 +466,14 @@ class PowerMaxProvisionTest(test.TestCase):
array, snap_name, source_group_name)
@mock.patch.object(rest.PowerMaxRest,
'get_storagegroup_snap_generation_list',
side_effect=[['0', '3', '1', '2'],
['0', '1'], ['0'], list()])
'get_storage_group_snap_id_list',
side_effect=[[tpd.PowerMaxData.snap_id,
tpd.PowerMaxData.snap_id_2,
tpd.PowerMaxData.snap_id,
tpd.PowerMaxData.snap_id_2],
[tpd.PowerMaxData.snap_id,
tpd.PowerMaxData.snap_id_2],
[tpd.PowerMaxData.snap_id], list()])
def test_delete_group_replica_side_effect(self, mock_list):
array = self.data.array
snap_name = self.data.group_snapshot_name
@ -532,14 +544,14 @@ class PowerMaxProvisionTest(test.TestCase):
def test_delete_volume_snap_check_for_links(self, mock_unlink, mock_tgts):
self.provision.delete_volume_snap_check_for_links(
self.data.array, self.data.test_snapshot_snap_name,
self.data.device_id, self.data.extra_specs)
self.data.device_id, self.data.extra_specs, self.data.snap_id)
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, tpd.PowerMaxData.device_id2)],
generation=0)
self.data.extra_specs, snap_id=self.data.snap_id,
list_volume_pairs=[
(self.data.device_id, tpd.PowerMaxData.device_id2)])
mock_unlink.reset_mock()
self.provision.delete_volume_snap_check_for_links(
self.data.array, self.data.test_snapshot_snap_name,
self.data.device_id, self.data.extra_specs)
self.data.device_id, self.data.extra_specs, self.data.snap_id)
self.assertEqual(2, mock_unlink.call_count)

View File

@ -47,6 +47,7 @@ class PowerMaxRestTest(test.TestCase):
self.driver = driver
self.common = self.driver.common
self.rest = self.common.rest
self.rest.is_snap_id = True
self.utils = self.common.utils
def test_rest_request_no_response(self):
@ -725,19 +726,15 @@ class PowerMaxRestTest(test.TestCase):
def test_delete_volume(self):
device_id = self.data.device_id
ucode_5978_foxtail = tpd.PowerMaxData.ucode_5978_foxtail
self.rest.ucode_major_level = utils.UCODE_5978
self.rest.ucode_minor_level = utils.UCODE_5978_HICKORY
with mock.patch.object(
self.rest, 'delete_resource') as mock_delete, (
mock.patch.object(
self.rest, '_modify_volume')) as mock_modify, (
mock.patch.object(
self.rest, 'get_array_detail',
return_value=ucode_5978_foxtail))as mock_det:
self.rest, '_modify_volume')) as mock_modify:
self.rest.delete_volume(self.data.array, device_id)
detail_call_count = mock_det.call_count
mod_call_count = mock_modify.call_count
self.assertEqual(1, detail_call_count)
self.assertEqual(1, mod_call_count)
mock_delete.assert_called_once_with(
self.data.array, 'sloprovisioning', 'volume', device_id)
@ -1243,58 +1240,62 @@ class PowerMaxRestTest(test.TestCase):
'copy': 'false', 'action': "",
'star': 'false', 'force': 'false',
'exact': 'false', 'remote': 'false',
'symforce': 'false', 'generation': 0}
'symforce': 'false', 'snap_id': self.data.snap_id}
payload_restore = {'deviceNameListSource': [{'name': source_id}],
'deviceNameListTarget': [{'name': source_id}],
'action': 'Restore',
'star': 'false', 'force': 'false'}
'star': 'false', 'force': 'false',
'snap_id': self.data.snap_id}
with mock.patch.object(
self.rest, 'modify_resource',
return_value=(202, self.data.job_list[0])) as mock_modify:
# link
payload['action'] = 'Link'
self.rest.modify_volume_snap(
array, source_id, target_id, snap_name, extra_specs, link=True)
array, source_id, target_id, snap_name, extra_specs,
self.data.snap_id, link=True)
mock_modify.assert_called_once_with(
array, 'replication', 'snapshot', payload,
resource_name=snap_name, private='/private')
# unlink
mock_modify.reset_mock()
payload['action'] = 'Unlink'
self.rest.modify_volume_snap(array, source_id, target_id,
snap_name, extra_specs, unlink=True)
self.rest.modify_volume_snap(
array, source_id, target_id, snap_name, extra_specs,
self.data.snap_id, unlink=True)
mock_modify.assert_called_once_with(
array, 'replication', 'snapshot', payload,
resource_name=snap_name, private='/private')
# restore
mock_modify.reset_mock()
payload['action'] = 'Restore'
self.rest.modify_volume_snap(array, source_id, "", snap_name,
extra_specs, unlink=False,
restore=True)
self.rest.modify_volume_snap(
array, source_id, "", snap_name, extra_specs,
self.data.snap_id, unlink=False, restore=True)
mock_modify.assert_called_once_with(
array, 'replication', 'snapshot', payload_restore,
resource_name=snap_name, private='/private')
# link or unlink, list of volumes
mock_modify.reset_mock()
payload['action'] = 'Link'
self.rest.modify_volume_snap(array, "", "", snap_name, extra_specs,
unlink=False, link=True,
list_volume_pairs=[(source_id,
self.rest.modify_volume_snap(
array, "", "", snap_name, extra_specs, self.data.snap_id,
unlink=False, link=True, list_volume_pairs=[(source_id,
target_id)])
mock_modify.assert_called_once_with(
array, 'replication', 'snapshot', payload,
resource_name=snap_name, private='/private')
# none selected
mock_modify.reset_mock()
self.rest.modify_volume_snap(array, source_id, target_id,
snap_name, extra_specs)
self.rest.modify_volume_snap(
array, source_id, target_id, snap_name, extra_specs,
self.data.snap_id)
mock_modify.assert_not_called()
# copy mode is True
payload['copy'] = 'true'
self.rest.modify_volume_snap(
array, source_id, target_id, snap_name, extra_specs, link=True,
copy=True)
array, source_id, target_id, snap_name, extra_specs,
self.data.snap_id, link=True, copy=True)
mock_modify.assert_called_once_with(
array, 'replication', 'snapshot', payload,
resource_name=snap_name, private='/private')
@ -1304,11 +1305,10 @@ class PowerMaxRestTest(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}],
'generation': 0}
generation = 0
'snap_id': self.data.snap_id}
with mock.patch.object(self.rest, 'delete_resource') as mock_delete:
self.rest.delete_volume_snap(
array, snap_name, source_device_id, generation)
array, snap_name, source_device_id, self.data.snap_id)
mock_delete.assert_called_once_with(
array, 'replication', 'snapshot', snap_name,
payload=payload, private='/private')
@ -1318,10 +1318,11 @@ class PowerMaxRestTest(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}],
'restore': True, 'generation': 0}
'restore': True, 'snap_id': self.data.snap_id}
with mock.patch.object(self.rest, 'delete_resource') as mock_delete:
self.rest.delete_volume_snap(
array, snap_name, source_device_id, restored=True)
array, snap_name, source_device_id, self.data.snap_id,
restored=True)
mock_delete.assert_called_once_with(
array, 'replication', 'snapshot', snap_name,
payload=payload, private='/private')
@ -1335,23 +1336,27 @@ class PowerMaxRestTest(test.TestCase):
def test_get_volume_snap(self):
array = self.data.array
snap_id = self.data.snap_id
snap_name = self.data.volume_snap_vx['snapshotSrcs'][0]['snapshotName']
device_id = self.data.device_id
ref_snap = self.data.volume_snap_vx['snapshotSrcs'][0]
snap = self.rest.get_volume_snap(array, device_id, snap_name)
snap = self.rest.get_volume_snap(array, device_id, snap_name, snap_id)
self.assertEqual(ref_snap, snap)
def test_get_volume_snap_none(self):
array = self.data.array
snap_id = self.data.snap_id
snap_name = self.data.volume_snap_vx['snapshotSrcs'][0]['snapshotName']
device_id = self.data.device_id
with mock.patch.object(self.rest, 'get_volume_snap_info',
return_value=None):
snap = self.rest.get_volume_snap(array, device_id, snap_name)
snap = self.rest.get_volume_snap(
array, device_id, snap_name, snap_id)
self.assertIsNone(snap)
with mock.patch.object(self.rest, 'get_volume_snap_info',
return_value={'snapshotSrcs': []}):
snap = self.rest.get_volume_snap(array, device_id, snap_name)
snap = self.rest.get_volume_snap(
array, device_id, snap_name, snap_id)
self.assertIsNone(snap)
def test_get_snap_linked_device_dict_list(self):
@ -1360,8 +1365,8 @@ class PowerMaxRestTest(test.TestCase):
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': [
'snap_name': snap_name, 'snapid': self.data.snap_id}]
ref_snap_list = [{'snapid': self.data.snap_id, 'linked_vols': [
{'state': 'Copied', 'target_device': '00001'}]}]
with mock.patch.object(self.rest, '_find_snap_vx_source_sessions',
return_value=snap_list):
@ -1372,26 +1377,25 @@ class PowerMaxRestTest(test.TestCase):
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['snapshotSrcs'][0]['snapshotName']
ref_sync = self.data.volume_snap_vx[
'snapshotSrcs'][0]['linkedDevices'][0]
sync = self.rest.get_sync_session(
array, source_id, snap_name, target_id, generation)
array, source_id, snap_name, target_id, self.data.snap_id)
self.assertEqual(ref_sync, sync)
def test_find_snap_vx_sessions(self):
array = self.data.array
source_id = self.data.device_id
ref_sessions = [{'generation': 0,
ref_sessions = [{'snapid': self.data.snap_id,
'snap_name': 'temp-000AA-snapshot_for_clone',
'source_vol_id': self.data.device_id,
'target_vol_id': self.data.device_id2,
'expired': False, 'copy_mode': True,
'state': 'Copied'},
{'generation': 1,
{'snapid': self.data.snap_id_2,
'snap_name': 'temp-000AA-snapshot_for_clone',
'source_vol_id': self.data.device_id,
'target_vol_id': self.data.device_id3,
@ -1411,7 +1415,8 @@ class PowerMaxRestTest(test.TestCase):
def test_find_snap_vx_sessions_tgt_only(self, mck_snap, mck_vol):
array = self.data.array
source_id = self.data.device_id
ref_session = {'generation': 6, 'state': 'Linked', 'copy_mode': False,
ref_session = {'snapid': self.data.snap_id, 'state': 'Linked',
'copy_mode': False,
'snap_name': 'temp-000AA-snapshot_for_clone',
'source_vol_id': self.data.device_id2,
'target_vol_id': source_id, 'expired': True}
@ -1592,14 +1597,15 @@ class PowerMaxRestTest(test.TestCase):
array, source_group, snap_name, '0')
@mock.patch.object(rest.PowerMaxRest, 'get_resource',
return_value={'generations': ['0', '1']})
def test_get_storagegroup_snap_generation_list(self, mock_list):
return_value={'snapids': [tpd.PowerMaxData.snap_id,
tpd.PowerMaxData.snap_id_2]})
def test_get_storagegroup_snap_id_list(self, mock_list):
array = self.data.array
source_group = self.data.storagegroup_name_source
snap_name = self.data.group_snapshot_name
ret_list = self.rest.get_storagegroup_snap_generation_list(
ret_list = self.rest.get_storage_group_snap_id_list(
array, source_group, snap_name)
self.assertEqual(['0', '1'], ret_list)
self.assertEqual([self.data.snap_id, self.data.snap_id_2], ret_list)
def test_get_storagegroup_rdf_details(self):
details = self.rest.get_storagegroup_rdf_details(
@ -1650,8 +1656,8 @@ class PowerMaxRestTest(test.TestCase):
new_snap_backend_name = self.data.managed_snap_id
self.rest.modify_volume_snap(
array, source_id, source_id, old_snap_backend_name,
self.data.extra_specs, link=False, unlink=False,
rename=True, new_snap_name=new_snap_backend_name)
self.data.extra_specs, self.data.snap_id, link=False,
unlink=False, rename=True, new_snap_name=new_snap_backend_name)
mock_modify.assert_called_once()
def test_get_private_volume_list_pass(self):
@ -1733,6 +1739,7 @@ class PowerMaxRestTest(test.TestCase):
'RestServerPort': '8443',
'RestUserName': 'user_test',
'RestPassword': 'pass_test',
'SerialNumber': self.data.array,
'SSLVerify': True,
}
self.rest.set_rest_credentials(array_info)

View File

@ -710,15 +710,17 @@ class PowerMaxCommon(object):
model_update = {
'provider_location': six.text_type(snapshot_dict)}
snapshot_metadata = self.get_snapshot_metadata(
extra_specs.get('array'), snapshot_dict.get('source_id'),
snapshot_dict.get('snap_name'))
model_update = self.update_metadata(
model_update, snapshot.metadata, self.get_snapshot_metadata(
extra_specs['array'], snapshot_dict['source_id'],
snapshot_dict['snap_name']))
model_update, snapshot.metadata, snapshot_metadata)
if snapshot.metadata:
model_update['metadata'].update(snapshot.metadata)
snapshot_metadata.update(
{'snap_display_name': snapshot_dict.get('snap_name')})
self.volume_metadata.capture_snapshot_info(
volume, extra_specs, 'createSnapshot', snapshot_dict['snap_name'])
volume, extra_specs, 'createSnapshot', snapshot_metadata)
return model_update
@ -731,7 +733,7 @@ class PowerMaxCommon(object):
LOG.info("Delete Snapshot: %(snapshotName)s.",
{'snapshotName': snapshot.name})
extra_specs = self._initial_setup(volume)
sourcedevice_id, snap_name = self._parse_snap_info(
sourcedevice_id, snap_name, snap_id_list = self._parse_snap_info(
extra_specs[utils.ARRAY], snapshot)
if not sourcedevice_id and not snap_name:
# Check if legacy snapshot
@ -745,9 +747,10 @@ class PowerMaxCommon(object):
LOG.info("No snapshot found on the array")
else:
# Ensure snap has not been recently deleted
self.provision.delete_volume_snap_check_for_links(
extra_specs[utils.ARRAY], snap_name,
sourcedevice_id, extra_specs)
for snap_id in snap_id_list:
self.provision.delete_volume_snap_check_for_links(
extra_specs[utils.ARRAY], snap_name,
sourcedevice_id, extra_specs, snap_id)
LOG.info("Leaving delete_snapshot: %(ssname)s.",
{'ssname': snap_name})
@ -1909,7 +1912,7 @@ class PowerMaxCommon(object):
message=exception_message)
if from_snapvx:
source_device_id, snap_name = self._parse_snap_info(
source_device_id, snap_name, __ = self._parse_snap_info(
array, source_volume)
else:
source_device_id = self._find_device_on_array(
@ -1946,10 +1949,13 @@ class PowerMaxCommon(object):
:param array: the array serial number
:param snapshot: the snapshot object
:returns: sourcedevice_id, foundsnap_name
:returns: sourcedevice_id -- str
foundsnap_name -- str
found_snap_id_list -- list
"""
foundsnap_name = None
sourcedevice_id = None
found_snap_id_list = list()
volume_name = snapshot.id
loc = snapshot.provider_location
@ -1962,19 +1968,21 @@ class PowerMaxCommon(object):
except KeyError:
LOG.info("Error retrieving snapshot details. Assuming "
"legacy structure of snapshot...")
return None, None
# Ensure snapvx is on the array.
return None, None, None
try:
snap_details = self.rest.get_volume_snap(
snap_detail_list = self.rest.get_volume_snaps(
array, sourcedevice_id, snap_name)
if snap_details:
for snap_details in snap_detail_list:
foundsnap_name = snap_name
found_snap_id_list.append(snap_details.get(
'snap_id') if self.rest.is_snap_id else (
snap_details.get('generation')))
except Exception as e:
LOG.info("Exception in retrieving snapshot: %(e)s.",
{'e': e})
foundsnap_name = None
if foundsnap_name is None or sourcedevice_id is None:
if not foundsnap_name or not sourcedevice_id or not found_snap_id_list:
LOG.debug("Error retrieving snapshot details. "
"Snapshot name: %(snap)s",
{'snap': volume_name})
@ -1982,9 +1990,10 @@ class PowerMaxCommon(object):
LOG.debug("Source volume: %(volume_name)s Snap name: "
"%(foundsnap_name)s.",
{'volume_name': sourcedevice_id,
'foundsnap_name': foundsnap_name})
'foundsnap_name': foundsnap_name,
'snap_ids': found_snap_id_list})
return sourcedevice_id, foundsnap_name
return sourcedevice_id, foundsnap_name, found_snap_id_list
def _create_snapshot(self, array, snapshot,
source_device_id, extra_specs):
@ -2859,24 +2868,24 @@ class PowerMaxCommon(object):
def _cleanup_target(
self, array, target_device_id, source_device_id,
clone_name, snap_name, extra_specs, generation=0,
target_volume=None):
clone_name, snap_name, extra_specs, target_volume=None):
"""Cleanup target volume on failed clone/ snapshot creation.
:param array: the array serial number
:param target_device_id: the target device ID
:param source_device_id: the source device ID
:param clone_name: the name of the clone volume
:param snap_name: the snapVX name
:param extra_specs: the extra specifications
:param generation: the generation number of the snapshot
:param target_volume: the target volume object
"""
snap_id = self.rest.get_snap_id(array, source_device_id, snap_name)
snap_session = self.rest.get_sync_session(
array, source_device_id, snap_name, target_device_id, generation)
array, source_device_id, snap_name, target_device_id, snap_id)
if snap_session:
self.provision.unlink_snapvx_tgt_volume(
array, target_device_id, source_device_id,
snap_name, extra_specs, generation)
snap_name, extra_specs, snap_id)
self._remove_vol_and_cleanup_replication(
array, target_device_id, clone_name, extra_specs, target_volume)
self._delete_from_srp(
@ -2937,7 +2946,8 @@ class PowerMaxCommon(object):
self._unlink_targets_and_delete_temp_snapvx(
tgt_session, array, extra_specs)
if src_sessions and not tgt_only:
src_sessions.sort(key=lambda k: k['generation'], reverse=True)
if not self.rest.is_snap_id:
src_sessions.sort(key=lambda k: k['snapid'], reverse=True)
for session in src_sessions:
self._unlink_targets_and_delete_temp_snapvx(
session, array, extra_specs)
@ -2952,7 +2962,7 @@ class PowerMaxCommon(object):
"""
snap_name = session.get('snap_name')
source = session.get('source_vol_id')
generation = session.get('generation')
snap_id = session.get('snapid')
expired = session.get('expired')
target, cm_enabled = None, False
@ -2964,24 +2974,24 @@ class PowerMaxCommon(object):
loop = True if cm_enabled else False
LOG.debug(
"Unlinking source from target. Source: %(vol)s, Target: "
"%(tgt)s, Generation: %(gen)s.", {'vol': source, 'tgt': target,
'gen': generation})
"%(tgt)s, Snap id: %(snapid)s.", {'vol': source, 'tgt': target,
'snapid': snap_id})
self.provision.unlink_snapvx_tgt_volume(
array, target, source, snap_name, extra_specs, generation,
array, target, source, snap_name, extra_specs, snap_id,
loop)
# Candidates for deletion:
# 1. If legacy snapshot with 'EMC_SMI' in snapshot name
# 2. If snapVX snapshot with copy mode enabled
# 3. If snapVX snapshot with copy mode disabled and not expired
if (snap_name and 'EMC_SMI' in snap_name or cm_enabled or (
if ('EMC_SMI' in snap_name or cm_enabled or (
not cm_enabled and not expired)):
LOG.debug(
"Deleting temporary snapshot. Source: %(vol)s, snap name: "
"%(name)s, generation: %(gen)s.", {
'vol': source, 'name': snap_name, 'gen': generation})
"%(name)s, snap id: %(snapid)s.", {
'vol': source, 'name': snap_name, 'snapid': snap_id})
self.provision.delete_temp_volume_snap(
array, snap_name, source, generation)
array, snap_name, source, snap_id)
def _get_target_source_device(self, array, device_id):
"""Get the source device id of the target.
@ -3027,8 +3037,6 @@ class PowerMaxCommon(object):
array, tgt_session, extra_specs, force_unlink)
count += 1
if src_sessions:
src_sessions.sort(
key=lambda k: k['generation'], reverse=True)
for session in src_sessions:
if count < self.snapvx_unlink_limit:
self._delete_valid_snapshot(
@ -3406,26 +3414,15 @@ class PowerMaxCommon(object):
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
if not self.rest.get_volume_snap(array, device_id, snap_name):
exception_message = (
_("Snapshot %(snap_name)s is not associated with specified "
"volume %(device_id)s, it is not possible to manage a "
"snapshot that is not associated with the specified "
"volume.")
% {'device_id': device_id, 'snap_name': snap_name})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
snap_id = self.rest.get_snap_id(array, device_id, snap_name)
snap_backend_name = self.utils.modify_snapshot_prefix(
snap_name, manage=True)
try:
self.rest.modify_volume_snap(
array, device_id, device_id, snap_name,
extra_specs, rename=True, new_snap_name=snap_backend_name)
extra_specs, snap_id=snap_id, rename=True,
new_snap_name=snap_backend_name)
except Exception as e:
exception_message = (
_("There was an issue managing %(snap_name)s, it was not "
@ -3439,15 +3436,19 @@ class PowerMaxCommon(object):
model_update = {
'display_name': snap_display_name,
'provider_location': six.text_type(prov_loc)}
snapshot_metadata = self.get_snapshot_metadata(
array, device_id, snap_backend_name)
model_update = self.update_metadata(
model_update, snapshot.metadata, self.get_snapshot_metadata(
array, device_id, snap_backend_name))
model_update, snapshot.metadata, snapshot_metadata)
LOG.info("Managing SnapVX Snapshot %(snap_name)s of source "
"volume %(device_id)s, OpenStack Snapshot display name: "
"%(snap_display_name)s", {
'snap_name': snap_name, 'device_id': device_id,
'snap_display_name': snap_display_name})
snapshot_metadata.update({'snap_display_name': snap_display_name})
self.volume_metadata.capture_snapshot_info(
volume, extra_specs, 'manageSnapshot', snapshot_metadata)
return model_update
@ -3474,7 +3475,17 @@ class PowerMaxCommon(object):
volume = snapshot.volume
extra_specs = self._initial_setup(volume)
array = extra_specs[utils.ARRAY]
device_id, snap_name = self._parse_snap_info(array, snapshot)
device_id, snap_name, snap_id_list = self._parse_snap_info(
array, snapshot)
if len(snap_id_list) != 1:
exception_message = (_(
"It is not possible to unmanage snapshot because there "
"are either multiple or no snapshots associated with "
"%(snap_name)s.") % {'snap_name': snap_name})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
if self.utils.is_volume_failed_over(volume):
exception_message = (
@ -3493,7 +3504,8 @@ class PowerMaxCommon(object):
try:
self.rest.modify_volume_snap(
array, device_id, device_id, snap_name, extra_specs,
rename=True, new_snap_name=new_snap_backend_name)
snap_id=snap_id_list[0], rename=True,
new_snap_name=new_snap_backend_name)
except Exception as e:
exception_message = (
_("There was an issue unmanaging Snapshot, it "
@ -3646,7 +3658,8 @@ class PowerMaxCommon(object):
if not volumes:
LOG.info("There were no volumes found on the backend "
"PowerMax/VMAX. You need to create some volumes "
"before snashot can be created and managed into Cinder.")
"before a snapshot can be created and managed into "
"Cinder.")
return manageable_snaps
for device in volumes:
@ -3757,8 +3770,9 @@ class PowerMaxCommon(object):
math.ceil(device['volumeHeader']['capGB'])),
'reason_not_safe': None, 'cinder_id': None,
'extra_info': {
'generation': snap_info['generation'],
'secured': snap_info['secured'],
'generation': snap_info.get('generation'),
'snap_id': snap_info.get('snapid'),
'secured': snap_info.get('secured'),
'timeToLive': human_ttl_timestamp,
'timestamp': human_timestamp},
'source_reference': {'source-id': snap_info['device']}}
@ -5559,7 +5573,7 @@ class PowerMaxCommon(object):
"""
src_dev_ids = []
for snap in snapshots:
src_dev_id, snap_name = self._parse_snap_info(array, snap)
src_dev_id, snap_name, __ = self._parse_snap_info(array, snap)
if snap_name:
src_dev_ids.append(src_dev_id)
return src_dev_ids
@ -5908,7 +5922,7 @@ class PowerMaxCommon(object):
if not source_vols:
for snap in snapshots:
if snap.id == volume.snapshot_id:
src_dev_id, __ = self._parse_snap_info(
src_dev_id, __, __ = self._parse_snap_info(
extra_specs[utils.ARRAY], snap)
vol_size = snap.volume_size
else:
@ -6311,30 +6325,41 @@ class PowerMaxCommon(object):
LOG.error(exception_message)
raise exception.VolumeDriverException(message=exception_message)
array = extra_specs[utils.ARRAY]
sourcedevice_id, snap_name = self._parse_snap_info(
sourcedevice_id, snap_name, snap_id_list = self._parse_snap_info(
array, snapshot)
if not sourcedevice_id or not snap_name:
LOG.error("No snapshot found on the array")
exception_message = (_(
"Failed to revert the volume to the snapshot"))
raise exception.VolumeDriverException(message=exception_message)
if len(snap_id_list) != 1:
exception_message = (_(
"It is not possible to revert snapshot because there are "
"either multiple or no snapshots associated with "
"%(snap_name)s.") % {'snap_name': snap_name})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
else:
snap_id = snap_id_list[0]
self._clone_check(array, sourcedevice_id, extra_specs)
try:
LOG.info("Reverting device: %(deviceid)s "
"to snapshot: %(snapname)s.",
{'deviceid': sourcedevice_id, 'snapname': snap_name})
self.provision.revert_volume_snapshot(
array, sourcedevice_id, snap_name, extra_specs)
array, sourcedevice_id, snap_name, snap_id, extra_specs)
# Once the restore is done, we need to check if it is complete
restore_complete = self.provision.is_restore_complete(
array, sourcedevice_id, snap_name, extra_specs)
array, sourcedevice_id, snap_name, snap_id, extra_specs)
if not restore_complete:
LOG.debug("Restore couldn't complete in the specified "
"time interval. The terminate restore may fail")
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, generation=0)
array, snap_name, sourcedevice_id, snap_id,
restored=True)
# Revert volume to snapshot is successful if termination was
# successful - possible even if restore_complete was False
# when we checked last.
@ -6449,12 +6474,22 @@ class PowerMaxCommon(object):
:param snap_name: the snapshot name
:returns: dict -- volume metadata
"""
snap_id_list = list()
snap_info = self.rest.get_volume_snap_info(array, device_id)
device_name = snap_info['deviceName']
device_name = snap_info.get('deviceName')
snapshot_src_list = snap_info.get('snapshotSrcs')
for snapshot_src in snapshot_src_list:
if snap_name == snapshot_src.get('snapshotName'):
snap_id_list.append(snapshot_src.get(
'snap_id') if self.rest.is_snap_id else snapshot_src.get(
'generation'))
device_label = device_name.split(':')[1]
metadata = {'SnapshotLabel': snap_name,
'SourceDeviceID': device_id,
'SourceDeviceLabel': device_label}
'SourceDeviceLabel': device_label,
'SnapIdList': ', '.join(
six.text_type(v) for v in snap_id_list),
'is_snap_id': self.rest.is_snap_id}
return metadata

View File

@ -127,6 +127,7 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
- Support for Port Group and Port load balancing
(bp powermax-port-load-balance)
- Fix to enable legacy volumes to live migrate (#1867163)
- Use of snap id instead of generation (bp powermax-snapset-ids)
"""
VERSION = "4.3.0"

View File

@ -133,6 +133,7 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
- Support for Port Group and Port load balancing
(bp powermax-port-load-balance)
- Fix to enable legacy volumes to live migrate (#1867163)
- Use of snap id instead of generation (bp powermax-snapset-ids)
"""
VERSION = "4.3.0"

View File

@ -387,17 +387,28 @@ class PowerMaxVolumeMetadata(object):
@debug_required
def capture_snapshot_info(
self, source, extra_specs, successful_operation, last_ss_name):
self, source, extra_specs, successful_operation,
snapshot_metadata):
"""Captures snapshot info in volume metadata
:param source: the source volume object
:param extra_specs: extra specifications
:param successful_operation: snapshot operation
:param last_ss_name: the last snapshot name
:param snapshot_metadata: snapshot metadata
"""
last_ss_name, snapshot_label, source_device_id = None, None, None
source_device_label, snap_ids, is_snap_id = None, None, None
if isinstance(source, volume.Volume):
if 'create' in successful_operation:
if 'create' or 'manage' in successful_operation:
snapshot_count = six.text_type(len(source.snapshots))
if snapshot_metadata:
last_ss_name = snapshot_metadata.get('snap_display_name')
snapshot_label = snapshot_metadata.get('SnapshotLabel')
source_device_id = snapshot_metadata.get('SourceDeviceID')
source_device_label = snapshot_metadata.get(
'SourceDeviceLabel')
snap_ids = snapshot_metadata.get('SnapIdList')
is_snap_id = snapshot_metadata.get('is_snap_id')
else:
snapshot_count = six.text_type(len(source.snapshots) - 1)
default_sg = (
@ -414,7 +425,12 @@ class PowerMaxVolumeMetadata(object):
self.utils.get_volume_element_name(source.id)),
openstack_name=source.display_name,
snapshot_count=snapshot_count,
last_ss_name=last_ss_name)
last_ss_name=last_ss_name,
snapshot_label=snapshot_label,
is_snap_id=is_snap_id,
snap_ids_or_gens=snap_ids,
source_device_id=source_device_id,
source_device_label=source_device_label)
volume_metadata = self.update_volume_info_metadata(
datadict, self.version_dict)
self.print_pretty_table(volume_metadata)

View File

@ -184,7 +184,7 @@ class PowerMaxProvision(object):
def unlink_snapvx_tgt_volume(
self, array, target_device_id, source_device_id, snap_name,
extra_specs, generation=0, loop=True):
extra_specs, snap_id, loop=True):
"""Unlink a snapshot from its target volume.
:param array: the array serial number
@ -192,7 +192,7 @@ class PowerMaxProvision(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
:param snap_id: the unique snap id of the SnapVX
:param loop: if looping call is required for handling retries
"""
@coordination.synchronized("emc-snapvx-{src_device_id}")
@ -202,15 +202,14 @@ class PowerMaxProvision(object):
{'src': src_device_id, 'tgt': target_device_id})
self._unlink_volume(array, src_device_id, target_device_id,
snap_name, extra_specs,
list_volume_pairs=None, generation=generation,
loop=loop)
snap_name, extra_specs, snap_id=snap_id,
list_volume_pairs=None, loop=loop)
do_unlink_volume(source_device_id)
def _unlink_volume(
self, array, source_device_id, target_device_id, snap_name,
extra_specs, list_volume_pairs=None, generation=0, loop=True):
extra_specs, snap_id=None, list_volume_pairs=None, loop=True):
"""Unlink a target volume from its source volume.
:param array: the array serial number
@ -218,8 +217,8 @@ class PowerMaxProvision(object):
:param target_device_id: the target device id
:param snap_name: the snap name
:param extra_specs: extra specifications
:param snap_id: the unique snap id of the SnapVX
:param list_volume_pairs: list of volume pairs, optional
:param generation: the generation number of the snapshot
:param loop: if looping call is required for handling retries
:returns: return code
"""
@ -234,9 +233,8 @@ class PowerMaxProvision(object):
if not kwargs['modify_vol_success']:
self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name,
extra_specs, unlink=True,
list_volume_pairs=list_volume_pairs,
generation=generation)
extra_specs, snap_id=snap_id, unlink=True,
list_volume_pairs=list_volume_pairs)
kwargs['modify_vol_success'] = True
except exception.VolumeBackendAPIException:
pass
@ -251,9 +249,8 @@ class PowerMaxProvision(object):
if not loop:
self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name,
extra_specs, unlink=True,
list_volume_pairs=list_volume_pairs,
generation=generation)
extra_specs, snap_id=snap_id, unlink=True,
list_volume_pairs=list_volume_pairs)
else:
kwargs = {'retries': 0,
'modify_vol_success': False}
@ -262,31 +259,40 @@ class PowerMaxProvision(object):
return rc
def delete_volume_snap(self, array, snap_name,
source_device_id, restored=False, generation=0):
source_device_ids, snap_id=None, restored=False):
"""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 source_device_ids: the source device ids
:param snap_id: the unique snap id of the SnapVX
:param restored: Flag to indicate if restored session is being deleted
:param generation: the snapshot generation number
"""
@coordination.synchronized("emc-snapvx-{src_device_id}")
def do_delete_volume_snap(src_device_id):
LOG.debug("Delete SnapVx: %(snap_name)s for volume %(vol)s.",
{'vol': src_device_id, 'snap_name': snap_name})
LOG.debug("Delete SnapVx: %(snap_name)s for source %(src)s and "
"devices %(devs)s.",
{'snap_name': snap_name, 'src': src_device_id,
'devs': source_device_ids})
self.rest.delete_volume_snap(
array, snap_name, src_device_id, restored, generation)
array, snap_name, source_device_ids, snap_id=snap_id,
restored=restored)
do_delete_volume_snap(source_device_id)
device_id = source_device_ids[0] if isinstance(
source_device_ids, list) else source_device_ids
if snap_id is None:
snap_id = self.rest.get_snap_id(array, device_id, snap_name)
do_delete_volume_snap(device_id)
def is_restore_complete(self, array, source_device_id,
snap_name, extra_specs):
snap_name, snap_id, extra_specs):
"""Check and wait for a restore to complete
:param array: the array serial number
:param source_device_id: source device id
:param snap_name: snapshot name
:param snap_id: unique snap id
:param extra_specs: extra specification
:returns: bool
"""
@ -302,7 +308,7 @@ class PowerMaxProvision(object):
kwargs['retries'] = retries + 1
if not kwargs['wait_for_restore_called']:
if self._is_restore_complete(
array, source_device_id, snap_name):
array, source_device_id, snap_name, snap_id):
kwargs['wait_for_restore_called'] = True
except Exception:
exception_message = (_("Issue encountered waiting for "
@ -325,17 +331,19 @@ class PowerMaxProvision(object):
rc = timer.start(interval=int(extra_specs[utils.INTERVAL])).wait()
return rc
def _is_restore_complete(self, array, source_device_id, snap_name):
def _is_restore_complete(
self, array, source_device_id, snap_name, snap_id):
"""Helper function to check if restore is complete.
:param array: the array serial number
:param source_device_id: source device id
:param snap_name: the snapshot name
:param snap_id: unique snap id
:returns: restored -- bool
"""
restored = False
snap_details = self.rest.get_volume_snap(
array, source_device_id, snap_name)
array, source_device_id, snap_name, snap_id)
if snap_details:
linked_devices = snap_details.get("linkedDevices", [])
for linked_device in linked_devices:
@ -347,7 +355,7 @@ class PowerMaxProvision(object):
return restored
def delete_temp_volume_snap(self, array, snap_name,
source_device_id, generation=0):
source_device_id, snap_id):
"""Delete the temporary snapshot created for clone operations.
There can be instances where the source and target both attempt to
@ -356,17 +364,17 @@ class PowerMaxProvision(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
:param snap_id: the unique snap id of the SnapVX
"""
snapvx = self.rest.get_volume_snap(
array, source_device_id, snap_name, generation)
array, source_device_id, snap_name, snap_id)
if snapvx:
self.delete_volume_snap(
array, snap_name, source_device_id,
restored=False, generation=generation)
array, snap_name, source_device_id, snap_id=snap_id,
restored=False)
def delete_volume_snap_check_for_links(
self, array, snap_name, source_devices, extra_specs, generation=0):
self, array, snap_name, source_devices, extra_specs, snap_id):
"""Check if a snap has any links before deletion.
If a snapshot has any links, break the replication relationship
@ -375,7 +383,7 @@ class PowerMaxProvision(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
:param snap_id: the unique snap id of the SnapVX
"""
list_device_pairs = []
if not isinstance(source_devices, list):
@ -385,7 +393,7 @@ class PowerMaxProvision(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, generation)
array, source_device, snap_name, snap_id)
if len(linked_list) == 1:
target_device = linked_list[0]['targetDevice']
list_device_pairs.append((source_device, target_device))
@ -394,15 +402,16 @@ class PowerMaxProvision(object):
# If a single source volume has multiple targets,
# we must unlink each target individually
target_device = link['targetDevice']
self._unlink_volume(array, source_device, target_device,
snap_name, extra_specs, generation)
self._unlink_volume(
array, source_device, target_device, snap_name,
extra_specs, snap_id=snap_id)
if list_device_pairs:
self._unlink_volume(array, "", "", snap_name, extra_specs,
list_volume_pairs=list_device_pairs,
generation=generation)
self._unlink_volume(
array, "", "", snap_name, extra_specs, snap_id=snap_id,
list_volume_pairs=list_device_pairs)
if source_devices:
self.delete_volume_snap(array, snap_name, source_devices,
restored=False, generation=generation)
self.delete_volume_snap(
array, snap_name, source_devices, snap_id, restored=False)
def extend_volume(self, array, device_id, new_size, extra_specs,
rdf_group=None):
@ -647,20 +656,22 @@ class PowerMaxProvision(object):
LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s "
"snapshot: %(snap_name)s.",
{'srcGroup': source_group_name, 'snap_name': snap_name})
gen_list = self.rest.get_storagegroup_snap_generation_list(
snap_id_list = self.rest.get_storage_group_snap_id_list(
array, source_group_name, snap_name)
if gen_list:
gen_list.sort(reverse=True)
for gen in gen_list:
if snap_id_list:
if not self.rest.is_snap_id:
snap_id_list.sort(reverse=True)
for snap_id in snap_id_list:
self.rest.delete_storagegroup_snap(
array, source_group_name, snap_name, gen)
array, source_group_name, snap_name, snap_id)
else:
LOG.debug("Unable to get generation number(s) for: %(srcGroup)s.",
LOG.debug("Unable to get snap ids for: %(srcGroup)s.",
{'srcGroup': source_group_name})
def link_and_break_replica(self, array, source_group_name,
target_group_name, snap_name, extra_specs,
list_volume_pairs, delete_snapshot=False):
list_volume_pairs, delete_snapshot=False,
snap_id=None):
"""Links a group snap and breaks the relationship.
:param array: the array serial
@ -670,6 +681,7 @@ class PowerMaxProvision(object):
:param extra_specs: extra specifications
:param list_volume_pairs: the list of volume pairs
:param delete_snapshot: delete snapshot flag
:param snap_id: the unique snapVx identifier
"""
LOG.debug("Linking Snap Vx snapshot: source group: %(srcGroup)s "
"targetGroup: %(tgtGroup)s.",
@ -696,17 +708,36 @@ class PowerMaxProvision(object):
self.delete_volume_snap(array, snap_name, source_devices)
def revert_volume_snapshot(self, array, source_device_id,
snap_name, extra_specs):
snap_name, snap_id, extra_specs):
"""Revert a volume snapshot
:param array: the array serial number
:param source_device_id: device id of the source
:param snap_name: snapvx snapshot name
:param snap_id: the unique snap identifier
:param extra_specs: the extra specifications
"""
start_time = time.time()
self.rest.modify_volume_snap(
array, source_device_id, "", snap_name, extra_specs, restore=True)
try:
self.rest.modify_volume_snap(
array, source_device_id, "", snap_name, extra_specs,
snap_id=snap_id, restore=True)
except exception.VolumeBackendAPIException as ex:
if utils.REVERT_SS_EXC in ex:
exception_message = _(
"Link must be fully copied for this operation to proceed. "
"Please reset the volume state from error to available "
"and wait for awhile before attempting another "
"revert to snapshot operation. You may want to delete "
"the latest snapshot taken in this revert to snapshot "
"operation, as you will only be able to revert to the "
"last snapshot.")
else:
exception_message = (_(
"Revert to snapshot failed with exception "
"%(e)s.") % {'e': ex})
raise exception.VolumeBackendAPIException(
message=exception_message)
LOG.debug("Restore volume snapshot took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(start_time,
time.time())})

View File

@ -81,6 +81,9 @@ class PowerMaxRest(object):
self.u4p_failover_backoff_factor = 1
self.u4p_in_failover = False
self.u4p_failover_lock = False
self.ucode_major_level = None
self.ucode_minor_level = None
self.is_snap_id = False
def set_rest_credentials(self, array_info):
"""Given the array record set the rest server credentials.
@ -96,6 +99,9 @@ class PowerMaxRest(object):
self.base_uri = ("https://%(ip_port)s/univmax/restapi" % {
'ip_port': ip_port})
self.session = self._establish_rest_session()
self.ucode_major_level, self.ucode_minor_level = (
self.get_major_minor_ucode(array_info['SerialNumber']))
self.is_snap_id = self._is_snapid_enabled()
def set_u4p_failover_config(self, failover_info):
"""Set the environment failover Unisphere targets and configuration..
@ -1522,18 +1528,9 @@ class PowerMaxRest(object):
:param array: the array serial number
:param device_id: volume device id
"""
array_details = self.get_array_detail(array)
ucode_major_level = 0
ucode_minor_level = 0
if array_details:
split_ucode_level = array_details['ucode'].split('.')
ucode_level = [int(level) for level in split_ucode_level]
ucode_major_level = ucode_level[0]
ucode_minor_level = ucode_level[1]
if ((ucode_major_level >= utils.UCODE_5978)
and (ucode_minor_level > utils.UCODE_5978_ELMSR)):
if ((self.ucode_major_level >= utils.UCODE_5978)
and (self.ucode_minor_level > utils.UCODE_5978_ELMSR)):
# Use Rapid TDEV Deallocation to delete after ELMSR
try:
# Rename volume, removing the OS-<cinderUUID>
@ -2074,9 +2071,9 @@ class PowerMaxRest(object):
job, extra_specs)
def modify_volume_snap(self, array, source_id, target_id, snap_name,
extra_specs, link=False, unlink=False,
extra_specs, snap_id=None, link=False, unlink=False,
rename=False, new_snap_name=None, restore=False,
list_volume_pairs=None, generation=0, copy=False):
list_volume_pairs=None, copy=False):
"""Modify a snapvx snapshot
:param array: the array serial number
@ -2084,13 +2081,13 @@ class PowerMaxRest(object):
:param target_id: the target device id
:param snap_name: the snapshot name
:param extra_specs: extra specifications
:param snap_id: the unique snap id of the snapVX
:param link: Flag to indicate action = Link
:param unlink: Flag to indicate action = Unlink
:param rename: Flag to indicate action = Rename
: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
:param copy: If copy mode should be used for SnapVX target links
"""
action, operation, payload = '', '', {}
@ -2105,7 +2102,6 @@ class PowerMaxRest(object):
elif restore:
action = "Restore"
payload = {}
if action == "Restore":
operation = 'Restore snapVx snapshot'
payload = {"deviceNameListSource": [{"name": source_id}],
@ -2127,14 +2123,18 @@ class PowerMaxRest(object):
"copy": copy, "action": action,
"star": 'false', "force": 'false',
"exact": 'false', "remote": 'false',
"symforce": 'false', "generation": generation}
"symforce": 'false'}
elif action == "Rename":
operation = 'Rename snapVx snapshot'
payload = {"deviceNameListSource": [{"name": source_id}],
"deviceNameListTarget": [{"name": source_id}],
"action": action, "newsnapshotname": new_snap_name}
if self.is_snap_id:
payload.update({"snap_id": snap_id}) if snap_id else (
payload.update({"generation": "0"}))
else:
payload.update({"generation": snap_id}) if snap_id else (
payload.update({"generation": "0"}))
if action:
status_code, job = self.modify_resource(
array, REPLICATION, 'snapshot', payload,
@ -2142,22 +2142,28 @@ class PowerMaxRest(object):
self.wait_for_job(operation, status_code, job, extra_specs)
def delete_volume_snap(self, array, snap_name,
source_device_ids, restored=False, generation=0):
source_device_ids, snap_id=None, restored=False):
"""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 snap_id: the unique snap id of the snapVX
: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,
"generation": int(generation)}
payload = {"deviceNameListSource": device_list}
if self.is_snap_id:
payload.update({"snap_id": snap_id}) if snap_id else (
payload.update({"generation": 0}))
else:
payload.update({"generation": snap_id}) if snap_id else (
payload.update({"generation": 0}))
if restored:
payload.update({"restore": True})
LOG.debug("The payload is %(payload)s.",
@ -2178,13 +2184,13 @@ class PowerMaxRest(object):
return self.get_resource(array, REPLICATION, 'volume',
resource_name, private='/private')
def get_volume_snap(self, array, device_id, snap_name, generation=0):
def get_volume_snap(self, array, device_id, snap_name, snap_id):
"""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
:param snap_id: the unique snap id of the snapVX
:returns: snapshot dict, or None
"""
snapshot = None
@ -2194,11 +2200,34 @@ class PowerMaxRest(object):
bool(snap_info['snapshotSrcs'])):
for snap in snap_info['snapshotSrcs']:
if snap['snapshotName'] == snap_name:
if snap['generation'] == generation:
snapshot = snap
break
if self.is_snap_id:
if snap['snap_id'] == snap_id:
snapshot = snap
break
else:
if snap['generation'] == snap_id:
snapshot = snap
break
return snapshot
def get_volume_snaps(self, array, device_id, snap_name):
"""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
:returns: snapshot dict, or None
"""
snapshots = list()
snap_info = self.get_volume_snap_info(array, device_id)
if snap_info:
if (snap_info.get('snapshotSrcs', None) and
bool(snap_info['snapshotSrcs'])):
for snap in snap_info['snapshotSrcs']:
if snap['snapshotName'] == snap_name:
snapshots.append(snap)
return snapshots
def get_volume_snapshot_list(self, array, source_device_id):
"""Get a list of snapshot details for a particular volume.
@ -2236,7 +2265,7 @@ class PowerMaxRest(object):
return snapvx_tgt, snapvx_src, rdf_grp
def is_sync_complete(self, array, source_device_id,
target_device_id, snap_name, extra_specs):
target_device_id, snap_name, extra_specs, snap_id):
"""Check if a sync session is complete.
:param array: the array serial number
@ -2244,6 +2273,7 @@ class PowerMaxRest(object):
:param target_device_id: target device id
:param snap_name: snapshot name
:param extra_specs: extra specifications
:param snap_id: the unique snap id of the SnapVX
:returns: bool
"""
@ -2259,7 +2289,7 @@ class PowerMaxRest(object):
if not kwargs['wait_for_sync_called']:
if self._is_sync_complete(
array, source_device_id, snap_name,
target_device_id):
target_device_id, snap_id):
kwargs['wait_for_sync_called'] = True
except Exception:
exception_message = (_("Issue encountered waiting for "
@ -2283,36 +2313,37 @@ class PowerMaxRest(object):
return rc
def _is_sync_complete(self, array, source_device_id, snap_name,
target_device_id):
target_device_id, snap_id):
"""Helper function to check if snapVx sync session is complete.
: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 snap_id: the unique snap id of the SnapVX
:returns: defined -- bool
"""
defined = True
session = self.get_sync_session(
array, source_device_id, snap_name, target_device_id)
array, source_device_id, snap_name, target_device_id, snap_id)
if session:
defined = session['defined']
return defined
def get_sync_session(self, array, source_device_id, snap_name,
target_device_id, generation=0):
target_device_id, snap_id):
"""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
:param snap_id: the unique snapid 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, generation)
array, source_device_id, snap_name, snap_id)
for target in linked_device_list:
if target_device_id == target['targetDevice']:
session = target
@ -2330,23 +2361,25 @@ class PowerMaxRest(object):
snapshots = self.get_volume_snapshot_list(array, source_device_id)
for snapshot in snapshots:
try:
snap_id = snapshot['snap_id'] if self.is_snap_id else (
snapshot['generation'])
if bool(snapshot['linkedDevices']):
link_info = {'linked_vols': snapshot['linkedDevices'],
'snap_name': snapshot['snapshotName'],
'generation': snapshot['generation']}
'snapid': snap_id}
snap_dict_list.append(link_info)
except KeyError:
pass
return snap_dict_list
def get_snap_linked_device_list(self, array, source_device_id,
snap_name, generation=0, state=None):
snap_name, snap_id, 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
:param generation: the generation number of the snapshot
:param snap_id: the unique snapid of the snapshot
:param state: filter for state of the link
:returns: linked_device_list or empty list
"""
@ -2355,25 +2388,24 @@ class PowerMaxRest(object):
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']:
if snap_id == snap_dict['snapid']:
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
"""Get list of linked devices for all snap ids 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
:returns: list of dict of generations with linked devices
:returns: list of dict of snapids 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:
for linked_vol in snap['linked_vols']:
@ -2382,17 +2414,17 @@ class PowerMaxRest(object):
# 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']
snap_id = snap['snapid']
found = False
for snap_dict in snap_dict_list:
if generation == snap_dict['generation']:
if snap_id == snap_dict['snapid']:
snap_dict['linked_vols'].append(
linked_vol)
found = True
break
if not found:
snap_dict_list.append(
{'generation': generation,
{'snapid': snap_id,
'linked_vols': [linked_vol]})
return snap_dict_list
@ -2406,27 +2438,27 @@ class PowerMaxRest(object):
"""
snap_tgt_dict, snap_src_dict_list = dict(), list()
s_in = self.get_volume_snap_info(array, device_id)
snap_src = (
s_in['snapshotSrcs'] if s_in.get('snapshotSrcs') else list())
snap_tgt = (
s_in['snapshotLnks'][0] if s_in.get('snapshotLnks') else dict())
if snap_src and not tgt_only:
for session in snap_src:
snap_src_dict = dict()
snap_src_dict['source_vol_id'] = device_id
snap_src_dict['generation'] = session['generation']
snap_src_dict['snap_name'] = session['snapshotName']
snap_src_dict['expired'] = session['expired']
snap_src_dict['snapid'] = session.get(
'snap_id') if self.is_snap_id else session.get(
'generation')
snap_src_dict['snap_name'] = session.get('snapshotName')
snap_src_dict['expired'] = session.get('expired')
if session.get('linkedDevices'):
snap_src_link = session['linkedDevices'][0]
snap_src_dict['target_vol_id'] = snap_src_link[
'targetDevice']
snap_src_dict['copy_mode'] = snap_src_link['copy']
snap_src_dict['state'] = snap_src_link['state']
snap_src_link = session.get('linkedDevices')[0]
snap_src_dict['target_vol_id'] = snap_src_link.get(
'targetDevice')
snap_src_dict['copy_mode'] = snap_src_link.get('copy')
snap_src_dict['state'] = snap_src_link.get('state')
snap_src_dict_list.append(snap_src_dict)
@ -2449,8 +2481,9 @@ class PowerMaxRest(object):
'snapshotName')
snap_tgt_dict['expired'] = snap_tgt_link.get(
'expired')
snap_tgt_dict['generation'] = snap_tgt_link.get(
'generation')
snap_tgt_dict['snapid'] = snap_tgt_link.get(
'snapid') if self.is_snap_id else (
snap_tgt_link.get('generation'))
return snap_src_dict_list, snap_tgt_dict
@ -3056,41 +3089,44 @@ class PowerMaxRest(object):
job, extra_specs)
def delete_storagegroup_snap(self, array, source_group,
snap_name, generation='0'):
snap_name, snap_id):
"""Delete a snapVx snapshot of a storage group.
:param array: the array serial number
:param source_group: the source group name
:param snap_name: the name of the snapshot
:param generation: the generation number of the SnapVX
:param snap_id: the unique snap id of the SnapVX
"""
postfix_uri = "/snapid/%s" % snap_id if self.is_snap_id else (
"/generation/%s" % snap_id)
resource_name = ("%(sg_name)s/snapshot/%(snap_name)s"
"/generation/%(generation)s"
"%(postfix_uri)s"
% {'sg_name': source_group, 'snap_name': snap_name,
'generation': generation})
'postfix_uri': postfix_uri})
self.delete_resource(
array, REPLICATION, 'storagegroup', resource_name=resource_name)
def get_storagegroup_snap_generation_list(
def get_storage_group_snap_id_list(
self, array, source_group, snap_name):
"""Get a snapshot and its generation count information for an sg.
The most recent snapshot will have a gen number of 0. The oldest
snapshot will have a gen number = genCount - 1 (i.e. if there are 4
generations of particular snapshot, the oldest will have a gen num of
3).
"""Get a snapshot and its snapid count information for an sg.
:param array: name of the array -- str
:param source_group: name of the storage group -- str
:param snap_name: the name of the snapshot -- str
:returns: generation numbers -- list
:returns: snapids -- list
"""
resource_name = ("%(sg_name)s/snapshot/%(snap_name)s/generation"
% {'sg_name': source_group, 'snap_name': snap_name})
postfix_uri = "snapid" if self.is_snap_id else "generation"
resource_name = ("%(sg_name)s/snapshot/%(snap_name)s/%(postfix_uri)s"
% {'sg_name': source_group, 'snap_name': snap_name,
'postfix_uri': postfix_uri})
response = self.get_resource(array, REPLICATION, 'storagegroup',
resource_name=resource_name)
return response.get('generations', list()) if response else list()
if self.is_snap_id:
return response.get('snapids', list()) if response else list()
else:
return response.get('generations', list()) if response else list()
def get_storagegroup_rdf_details(self, array, storagegroup_name,
rdf_group_num):
@ -3277,3 +3313,59 @@ class PowerMaxRest(object):
"requirements.")
return unisphere_meets_min_req
def get_snap_id(self, array, device_id, snap_name):
"""Get the unique snap id for a particular snap name
:param array: the array serial number
:param device_id: the source device ID
:param snap_name: the user supplied snapVX name
:raises: VolumeBackendAPIException
:returns: snap_id -- str
"""
snapshots = self.get_volume_snaps(array, device_id, snap_name)
if not snapshots:
exception_message = (_(
"Snapshot %(snap_name)s is not associated with "
"specified volume %(device_id)s.") % {
'device_id': device_id, 'snap_name': snap_name})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
elif len(snapshots) > 1:
exception_message = (_(
"Snapshot %(snap_name)s is associated with more than "
"one snap id. No information available to choose "
"which one.") % {
'device_id': device_id, 'snap_name': snap_name})
LOG.error(exception_message)
raise exception.VolumeBackendAPIException(
message=exception_message)
else:
return snapshots[0].get('snap_id') if self.is_snap_id else (
snapshots[0].get('generation'))
def get_major_minor_ucode(self, array):
"""Get the major and minor parts of the ucode
:param array: the array serial number
:returns: ucode_major_level, ucode_minor_level -- str, str
"""
array_details = self.get_array_detail(array)
ucode_major_level = 0
ucode_minor_level = 0
if array_details:
split_ucode_level = array_details['ucode'].split('.')
ucode_level = [int(level) for level in split_ucode_level]
ucode_major_level = ucode_level[0]
ucode_minor_level = ucode_level[1]
return ucode_major_level, ucode_minor_level
def _is_snapid_enabled(self):
"""Check if array is snap_id enabled
:returns: boolean
"""
return (self.ucode_major_level >= utils.UCODE_5978 and
self.ucode_minor_level >= utils.UCODE_5978_HICKORY)

View File

@ -43,6 +43,7 @@ MAX_SRP_LENGTH = 16
TRUNCATE_5 = 5
TRUNCATE_27 = 27
UCODE_5978_ELMSR = 221
UCODE_5978_HICKORY = 660
UCODE_5978 = 5978
UPPER_HOST_CHARS = 16
UPPER_PORT_GROUP_CHARS = 12
@ -209,6 +210,9 @@ INST_ID = 'instanceId'
DIR_ID = 'directorId'
PORT_ID = 'portId'
# Revert snapshot exception
REVERT_SS_EXC = 'Link must be fully copied for this operation to proceed'
class PowerMaxUtils(object):
"""Utility class for Rest based PowerMax volume drivers.