PowerMax Driver - Temporary snapshot enhancements
Improvements to unlinking of volume snapshots and deletion of temporary snapshots created during volume cloning operations. Including: 1. Merging of sync and clone check functionality. 2. Removal of snapvx unlink limit use during snapshot unlinking. 3. Adding early exit to volume delete, group delete and retype in cases where volume snapshots still exist after attempting unlink and deletion of snapvx sessions. Change-Id: I60f399eccb32daf5127c64108d5331cf798b2600
This commit is contained in:
parent
47cdd944b3
commit
1a267bf6ff
@ -224,10 +224,10 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
model_update = self.common.create_volume(self.data.test_volume)
|
||||
self.assertEqual(ref_model_update, model_update)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata',
|
||||
return_value='')
|
||||
def test_create_volume_from_snapshot(self, mck_meta, mck_clone_chk):
|
||||
def test_create_volume_from_snapshot(self, mck_meta, mck_cleanup_snaps):
|
||||
ref_model_update = ({'provider_location': six.text_type(
|
||||
deepcopy(self.data.provider_location_snapshot))})
|
||||
model_update = self.common.create_volume_from_snapshot(
|
||||
@ -362,10 +362,14 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
mck_configure.assert_called_once()
|
||||
mck_resume.assert_called_once()
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata',
|
||||
return_value='')
|
||||
def test_cloned_volume(self, mck_meta, mck_clone_chk):
|
||||
def test_cloned_volume(self, mck_meta, mck_cleanup_snaps):
|
||||
array = self.data.array
|
||||
test_volume = self.data.test_clone_volume
|
||||
source_device_id = self.data.device_id
|
||||
extra_specs = self.common._initial_setup(test_volume)
|
||||
ref_model_update = ({'provider_location': six.text_type(
|
||||
self.data.provider_location_clone)})
|
||||
model_update = self.common.create_cloned_volume(
|
||||
@ -373,18 +377,55 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.assertEqual(
|
||||
ast.literal_eval(ref_model_update['provider_location']),
|
||||
ast.literal_eval(model_update['provider_location']))
|
||||
mck_cleanup_snaps.assert_called_once_with(
|
||||
array, source_device_id, extra_specs)
|
||||
|
||||
def test_delete_volume(self):
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
def test_delete_volume(self, mck_get_snaps):
|
||||
with mock.patch.object(self.common, '_delete_volume') as mock_delete:
|
||||
self.common.delete_volume(self.data.test_volume)
|
||||
mock_delete.assert_called_once_with(self.data.test_volume)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_from_srp')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap_info',
|
||||
return_value=tpd.PowerMaxData.volume_snap_vx)
|
||||
def test_delete_volume_fail_if_active_snapshots(
|
||||
self, mck_get_snaps, mck_cleanup, mck_delete):
|
||||
array = self.data.array
|
||||
test_volume = self.data.test_volume
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.common._initial_setup(test_volume)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common._delete_volume, test_volume)
|
||||
mck_cleanup.assert_called_once_with(array, device_id, extra_specs)
|
||||
mck_delete.assert_not_called()
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_from_srp')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(
|
||||
rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=('', tpd.PowerMaxData.snap_tgt_session_cm_enabled))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
def test_delete_volume_fail_if_snapvx_target(
|
||||
self, mck_get_snaps, mck_tgt_snap, mck_cleanup, mck_delete):
|
||||
array = self.data.array
|
||||
test_volume = self.data.test_volume
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.common._initial_setup(test_volume)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common._delete_volume, test_volume)
|
||||
mck_cleanup.assert_called_once_with(array, device_id, extra_specs)
|
||||
mck_delete.assert_not_called()
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_snapshot_metadata',
|
||||
return_value={'snap-meta-key-1': 'snap-meta-value-1',
|
||||
'snap-meta-key-2': 'snap-meta-value-2'})
|
||||
def test_create_snapshot(self, mck_meta, mck_clone_chk):
|
||||
def test_create_snapshot(self, mck_meta, mck_cleanup_snaps):
|
||||
ref_model_update = (
|
||||
{'provider_location': six.text_type(self.data.snap_location),
|
||||
'metadata': {'snap-meta-key-1': 'snap-meta-value-1',
|
||||
@ -1171,8 +1212,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
volume, connector, extra_specs)
|
||||
self.assertEqual('NONE', masking_view_dict[utils.WORKLOAD])
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_create_cloned_volume(self, mck_clone_chk):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_create_cloned_volume(self, mck_cleanup_snaps):
|
||||
volume = self.data.test_clone_volume
|
||||
source_volume = self.data.test_volume
|
||||
extra_specs = self.data.extra_specs
|
||||
@ -1182,8 +1223,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
volume, source_volume, extra_specs))
|
||||
self.assertEqual(ref_response, (clone_dict, rep_update, rep_info_dict))
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_create_cloned_volume_is_snapshot(self, mck_clone_chk):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_create_cloned_volume_is_snapshot(self, mck_cleanup_snaps):
|
||||
volume = self.data.test_snapshot
|
||||
source_volume = self.data.test_volume
|
||||
extra_specs = self.data.extra_specs
|
||||
@ -1193,8 +1234,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
volume, source_volume, extra_specs, True, False))
|
||||
self.assertEqual(ref_response, (clone_dict, rep_update, rep_info_dict))
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_create_cloned_volume_from_snapshot(self, mck_clone_chk):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_create_cloned_volume_from_snapshot(self, mck_cleanup_snaps):
|
||||
volume = self.data.test_clone_volume
|
||||
source_volume = self.data.test_snapshot
|
||||
extra_specs = self.data.extra_specs
|
||||
@ -1232,7 +1273,7 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
'_find_device_on_array',
|
||||
return_value=None)
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_clone_check')
|
||||
'_cleanup_device_snapvx')
|
||||
def test_create_cloned_volume_source_not_found(
|
||||
self, mock_check, mock_device):
|
||||
volume = self.data.test_clone_volume
|
||||
@ -1300,16 +1341,18 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.common._create_snapshot,
|
||||
array, snapshot, source_device_id, extra_specs)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(masking.PowerMaxMasking,
|
||||
'remove_vol_from_storage_group')
|
||||
def test_delete_volume_from_srp(self, mock_rm):
|
||||
def test_delete_volume_from_srp(self, mock_rm, mock_get_snaps):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
volume_name = self.data.test_volume.name
|
||||
ref_extra_specs = deepcopy(self.data.extra_specs_intervals_set)
|
||||
ref_extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||
volume = self.data.test_volume
|
||||
with mock.patch.object(self.common, '_sync_check'):
|
||||
with mock.patch.object(self.common, '_cleanup_device_snapvx'):
|
||||
with mock.patch.object(
|
||||
self.common, '_delete_from_srp') as mock_delete:
|
||||
self.common._delete_volume(volume)
|
||||
@ -2062,8 +2105,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
return_value=(False, False, False))
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_remove_vol_and_cleanup_replication')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_unmanage_success(self, mck_clone, mock_rm, mck_sess):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_unmanage_success(self, mck_cleanup_snaps, mock_rm, mck_sess):
|
||||
volume = self.data.test_volume
|
||||
with mock.patch.object(self.rest, 'rename_volume') as mock_rename:
|
||||
self.common.unmanage(volume)
|
||||
@ -2090,8 +2133,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(True, True, False))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_unmanage_temp_snapshot_links(self, mck_clone, mck_sess):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_unmanage_temp_snapshot_links(self, mck_cleanup_snaps, mck_sess):
|
||||
volume = self.data.test_volume
|
||||
self.assertRaises(exception.VolumeIsBusy, self.common.unmanage,
|
||||
volume)
|
||||
@ -2251,6 +2294,62 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
target_slo, target_workload, target_extra_specs)
|
||||
self.assertTrue(success)
|
||||
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'get_rep_config',
|
||||
return_value=tpd.PowerMaxData.rep_config_metro)
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled',
|
||||
side_effect=[False, True])
|
||||
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_slo_list', return_value=[])
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=[{'snapshotName': 'name',
|
||||
'linkedDevices': 'details'}])
|
||||
def test_migrate_to_metro_exception_on_linked_snapshot_source(
|
||||
self, mck_get, mck_slo, mck_validate, mck_rep, mck_config):
|
||||
array_id = self.data.array
|
||||
volume = self.data.test_volume
|
||||
device_id = self.data.device_id
|
||||
srp = self.data.srp
|
||||
target_slo = self.data.slo_silver
|
||||
target_workload = self.data.workload
|
||||
volume_name = volume.name
|
||||
target_extra_specs = self.data.rep_extra_specs_rep_config_metro
|
||||
new_type = {'extra_specs': target_extra_specs}
|
||||
extra_specs = self.data.extra_specs
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException, self.common._migrate_volume,
|
||||
array_id, volume, device_id, srp, target_slo, target_workload,
|
||||
volume_name, new_type, extra_specs)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'get_rep_config',
|
||||
return_value=tpd.PowerMaxData.rep_config_metro)
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled',
|
||||
side_effect=[False, True])
|
||||
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_slo_list', return_value=[])
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=[{'snapshotName': 'name'}])
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=('', {'source_vol_id': 'source_vol_id',
|
||||
'snap_name': 'snap_name'}))
|
||||
def test_migrate_to_metro_exception_on_snapshot_target(
|
||||
self, mck_find, mck_snap, mck_slo, mck_validate, mck_rep,
|
||||
mck_config, mck_cleanup):
|
||||
array_id = self.data.array
|
||||
volume = self.data.test_volume
|
||||
device_id = self.data.device_id
|
||||
srp = self.data.srp
|
||||
target_slo = self.data.slo_silver
|
||||
target_workload = self.data.workload
|
||||
volume_name = volume.name
|
||||
target_extra_specs = self.data.rep_extra_specs_rep_config_metro
|
||||
new_type = {'extra_specs': target_extra_specs}
|
||||
extra_specs = self.data.extra_specs
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException, self.common._migrate_volume,
|
||||
array_id, volume, device_id, srp, target_slo, target_workload,
|
||||
volume_name, new_type, extra_specs)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_post_retype_srdf_protect_storage_group',
|
||||
return_value=(True, True, True))
|
||||
@ -2821,8 +2920,10 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.common._update_group_promotion,
|
||||
group, add_vols, remove_vols)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(volume_utils, 'is_group_a_type', return_value=False)
|
||||
def test_delete_group(self, mock_check):
|
||||
def test_delete_group(self, mock_check, mck_snaps):
|
||||
group = self.data.test_group_1
|
||||
volumes = [self.data.test_volume]
|
||||
context = None
|
||||
@ -2837,8 +2938,10 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
context, group, volumes)
|
||||
self.assertEqual(ref_model_update, model_update)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(volume_utils, 'is_group_a_type', return_value=False)
|
||||
def test_delete_group_success(self, mock_check):
|
||||
def test_delete_group_success(self, mock_check, mck_get_snaps):
|
||||
group = self.data.test_group_1
|
||||
volumes = []
|
||||
ref_model_update = {'status': fields.GroupStatus.DELETED}
|
||||
@ -2850,7 +2953,9 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
model_update, __ = self.common._delete_group(group, volumes)
|
||||
self.assertEqual(ref_model_update, model_update)
|
||||
|
||||
def test_delete_group_already_deleted(self):
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
def test_delete_group_already_deleted(self, mck_get_snaps):
|
||||
group = self.data.test_group_failed
|
||||
ref_model_update = {'status': fields.GroupStatus.DELETED}
|
||||
volumes = []
|
||||
@ -2859,10 +2964,13 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
model_update, __ = self.common._delete_group(group, volumes)
|
||||
self.assertEqual(ref_model_update, model_update)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(volume_utils, 'is_group_a_type', return_value=False)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type',
|
||||
return_value=True)
|
||||
def test_delete_group_failed(self, mock_check, mock_type_check):
|
||||
def test_delete_group_failed(
|
||||
self, mock_check, mock_type_check, mck_get_snaps):
|
||||
group = self.data.test_group_1
|
||||
volumes = []
|
||||
ref_model_update = {'status': fields.GroupStatus.ERROR_DELETING}
|
||||
@ -2873,6 +2981,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
group, volumes)
|
||||
self.assertEqual(ref_model_update, model_update)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(volume_utils, 'is_group_a_type', return_value=False)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type',
|
||||
return_value=True)
|
||||
@ -2885,15 +2995,38 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
return_value= tpd.PowerMaxData.device_id)
|
||||
@mock.patch.object(masking.PowerMaxMasking,
|
||||
'remove_volumes_from_storage_group')
|
||||
def test_delete_group_clone_check(
|
||||
def test_delete_group_cleanup_snapvx(
|
||||
self, mock_rem, mock_find, mock_mems, mock_vols, mock_chk1,
|
||||
mock_chk2):
|
||||
mock_chk2, mck_get_snaps):
|
||||
group = self.data.test_group_1
|
||||
volumes = [self.data.test_volume_group_member]
|
||||
with mock.patch.object(
|
||||
self.common, '_clone_check') as mock_clone_chk:
|
||||
self.common, '_cleanup_device_snapvx') as mock_cleanup_snapvx:
|
||||
self.common._delete_group(group, volumes)
|
||||
mock_clone_chk.assert_called_once()
|
||||
mock_cleanup_snapvx.assert_called_once()
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=[{'snapshotName': 'name'}])
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_delete_group_with_volumes_exception_on_remaining_snapshots(
|
||||
self, mck_cleanup, mck_get):
|
||||
group = self.data.test_group_1
|
||||
volumes = [self.data.test_volume_group_member]
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common._delete_group, group, volumes)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=('', {'source_vol_id': 'id',
|
||||
'snap_name': 'name'}))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=None)
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_delete_group_with_volumes_exception_on_target_links(
|
||||
self, mck_cleanup, mck_get, mck_find):
|
||||
group = self.data.test_group_1
|
||||
volumes = [self.data.test_volume_group_member]
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common._delete_group, group, volumes)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'delete_storage_group')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_failover_replication',
|
||||
@ -3104,12 +3237,12 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.common.unmanage_snapshot(self.data.test_snapshot_manage)
|
||||
mock_mod.assert_called_once()
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'modify_volume_snap')
|
||||
def test_unmanage_snapshot_no_sync_check(self, mock_mod, mock_sync):
|
||||
def test_unmanage_snapshot_no_snapvx_cleanup(self, mock_mod, mock_cleanup):
|
||||
self.common.unmanage_snapshot(self.data.test_snapshot_manage)
|
||||
mock_mod.assert_called_once()
|
||||
mock_sync.assert_not_called()
|
||||
mock_cleanup.assert_not_called()
|
||||
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'is_volume_failed_over',
|
||||
return_value=True)
|
||||
@ -3133,7 +3266,7 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
@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(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'revert_volume_snapshot')
|
||||
def test_revert_to_snapshot(self, mock_revert, mock_clone,
|
||||
mock_complete, mock_delete, mock_parse):
|
||||
@ -3388,8 +3521,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_validation_checks_success(self, mck_sync, mck_rep):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_extend_vol_validation_checks_success(self, mck_cleanup, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
@ -3400,8 +3533,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_no_device(self, mck_sync, mck_rep):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_extend_vol_val_check_no_device(self, mck_cleanup, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = None
|
||||
@ -3414,8 +3547,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, True, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_snap_src(self, mck_sync, mck_rep):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_extend_vol_val_check_snap_src(self, mck_cleanup, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
@ -3429,8 +3562,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_wrong_size(self, mck_sync, mck_rep):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_extend_vol_val_check_wrong_size(self, mck_cleanup, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
@ -3585,103 +3718,6 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
port = self.common._get_unisphere_port()
|
||||
self.assertEqual(ref_port, port)
|
||||
|
||||
def test_sync_check(self):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
|
||||
with mock.patch.object(self.common, '_do_sync_check') as mck_sync:
|
||||
self.common._sync_check(array, device_id, extra_specs, False,
|
||||
self.data.device_id2)
|
||||
mck_sync.assert_called_with(array, self.data.device_id,
|
||||
extra_specs, False)
|
||||
mck_sync.reset_mock()
|
||||
with mock.patch.object(self.common, '_get_target_source_device',
|
||||
return_value=self.data.device_id3):
|
||||
self.common._sync_check(array, device_id, extra_specs, True)
|
||||
mck_sync.assert_called_with(array, self.data.device_id,
|
||||
extra_specs, True)
|
||||
mck_sync.reset_mock()
|
||||
self.common._sync_check(array, device_id, extra_specs)
|
||||
mck_sync.assert_called_with(array, device_id, extra_specs, False)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_unlink_targets_and_delete_temp_snapvx')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=(tpd.PowerMaxData.snap_src_sessions,
|
||||
tpd.PowerMaxData.snap_tgt_session))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(True, True, False))
|
||||
def test_do_sync_check(self, mck_rep, mck_find, mck_unlink):
|
||||
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common._do_sync_check(array, device_id, extra_specs)
|
||||
self.assertEqual(3, mck_unlink.call_count)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'delete_temp_volume_snap')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'unlink_snapvx_tgt_volume')
|
||||
def test_unlink_targets_and_delete_temp_snapvx_legacy(
|
||||
self, mck_break, mck_del):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = deepcopy(self.data.snap_tgt_session_cm_enabled)
|
||||
legacy_snap_name = 'EMC_SMI'
|
||||
session['snap_name'] = legacy_snap_name
|
||||
source = session['source_vol_id']
|
||||
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, legacy_snap_name,
|
||||
extra_specs, snap_id, True)
|
||||
mck_del.assert_called_once_with(
|
||||
array, legacy_snap_name, source, snap_id)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'delete_temp_volume_snap')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'unlink_snapvx_tgt_volume')
|
||||
def test_unlink_targets_and_delete_temp_snapvx_is_temp(
|
||||
self, mck_break, mck_del):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = deepcopy(self.data.snap_tgt_session_cm_enabled)
|
||||
snap_name = session['snap_name']
|
||||
source = session['source_vol_id']
|
||||
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, snap_id, True)
|
||||
mck_del.assert_called_once_with(array, snap_name, source, snap_id)
|
||||
|
||||
def test_unlink_targets_and_delete_temp_snapvx_no_snap_name_found(self):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = dict()
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common._unlink_targets_and_delete_temp_snapvx,
|
||||
session, array, extra_specs)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'delete_temp_volume_snap')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'unlink_snapvx_tgt_volume')
|
||||
def test_unlink_targets_and_delete_temp_snapvx_not_legacy_not_temp(
|
||||
self, mck_break, mck_del):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = deepcopy(self.data.snap_tgt_session_cm_enabled)
|
||||
session['snap_name'] = 'not_leg_or_tmp'
|
||||
snap_name = session['snap_name']
|
||||
source = session['source_vol_id']
|
||||
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, snap_id, True)
|
||||
mck_del.assert_not_called()
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=(None, tpd.PowerMaxData.snap_tgt_session))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
@ -3692,83 +3728,6 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
src_device = self.common._get_target_source_device(array, tgt_device)
|
||||
self.assertEqual(src_device, self.data.device_id)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_valid_snapshot')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=(tpd.PowerMaxData.snap_src_sessions,
|
||||
tpd.PowerMaxData.snap_tgt_session))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(True, True, False))
|
||||
def test_clone_check(self, mck_rep, mck_find, mck_del):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common.snapvx_unlink_limit = 3
|
||||
self.common._clone_check(array, device_id, extra_specs)
|
||||
self.assertEqual(3, mck_del.call_count)
|
||||
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, '_unlink_targets_and_delete_temp_snapvx')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=(tpd.PowerMaxData.snap_src_sessions,
|
||||
tpd.PowerMaxData.snap_tgt_session))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(True, True, False))
|
||||
def test_clone_check_force_unlink(self, mck_rep, mck_find, mck_del):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common.snapvx_unlink_limit = 3
|
||||
self.common._clone_check(
|
||||
array, device_id, extra_specs, force_unlink=True)
|
||||
self.assertEqual(3, mck_del.call_count)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_unlink_targets_and_delete_temp_snapvx')
|
||||
def test_delete_valid_snapshot(self, mck_unlink):
|
||||
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
|
||||
session = {'snap_name': 'EMC_SMI_TEST', 'expired': False}
|
||||
self.common._delete_valid_snapshot(array, session, extra_specs)
|
||||
mck_unlink.assert_called_with(session, array, extra_specs)
|
||||
mck_unlink.reset_mock()
|
||||
|
||||
session = {'snap_name': 'temp-000AA-snapshot_for_clone',
|
||||
'expired': True}
|
||||
self.common._delete_valid_snapshot(array, session, extra_specs)
|
||||
mck_unlink.assert_called_with(session, array, extra_specs)
|
||||
mck_unlink.reset_mock()
|
||||
|
||||
session = {'snap_name': 'temp-000AA-snapshot_for_clone',
|
||||
'expired': False}
|
||||
self.common._delete_valid_snapshot(array, session, extra_specs)
|
||||
mck_unlink.assert_not_called()
|
||||
|
||||
def test_delete_valid_snapshot_exception(self):
|
||||
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = {'snap_name': 'temp-000AA-snapshot_for_clone',
|
||||
'expired': True}
|
||||
|
||||
with mock.patch.object(
|
||||
self.common, '_unlink_targets_and_delete_temp_snapvx',
|
||||
side_effect=exception.VolumeBackendAPIException(
|
||||
"404 temp-000AA-snapshot_for_clone does not exist")
|
||||
) as mck_unlink:
|
||||
self.common._delete_valid_snapshot(array, session, extra_specs)
|
||||
mck_unlink.assert_called_with(session, array, extra_specs)
|
||||
|
||||
with mock.patch.object(
|
||||
self.common, '_unlink_targets_and_delete_temp_snapvx',
|
||||
side_effect=exception.VolumeBackendAPIException(
|
||||
"500 internal server error")):
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.common._unlink_targets_and_delete_temp_snapvx,
|
||||
array, session, extra_specs)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, '_get_private_volume',
|
||||
return_value=tpd.PowerMaxData.priv_vol_response_rep)
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||
@ -4339,3 +4298,110 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
rep_driver_data)
|
||||
self.assertIsNone(group_name)
|
||||
mock_group.assert_not_called()
|
||||
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, '_unlink_and_delete_temporary_snapshots')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'find_snap_vx_sessions',
|
||||
return_value=(None, 'tgt_session'))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(True, False, False))
|
||||
def test_cleanup_device_snapvx(self, mck_is_rep, mck_find, mck_unlink):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
mck_unlink.assert_called_once_with('tgt_session', array, extra_specs)
|
||||
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, '_unlink_and_delete_temporary_snapshots')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(False, False, False))
|
||||
def test_cleanup_device_snapvx_no_sessions(self, mck_is_rep, mck_unlink):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
mck_unlink.assert_not_called()
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_temp_snapshot')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_unlink_snapshot',
|
||||
return_value=True)
|
||||
def test_unlink_and_delete_temporary_snapshots_session_unlinked(
|
||||
self, mck_unlink, mck_delete):
|
||||
session = self.data.snap_tgt_session
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common._unlink_and_delete_temporary_snapshots(
|
||||
session, array, extra_specs)
|
||||
mck_unlink.assert_called_once_with(session, array, extra_specs)
|
||||
mck_delete.assert_called_once_with(session, array)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_temp_snapshot')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_unlink_snapshot',
|
||||
return_value=False)
|
||||
def test_unlink_and_delete_temporary_snapshots_session_not_unlinked(
|
||||
self, mck_unlink, mck_delete):
|
||||
session = self.data.snap_tgt_session
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
self.common._unlink_and_delete_temporary_snapshots(
|
||||
session, array, extra_specs)
|
||||
mck_unlink.assert_called_once_with(session, array, extra_specs)
|
||||
mck_delete.assert_not_called()
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'unlink_snapvx_tgt_volume')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap',
|
||||
side_effect=[tpd.PowerMaxData.priv_snap_response.get(
|
||||
'snapshotSrcs')[0], None])
|
||||
def test_unlink_temp_snapshot(self, mck_get, mck_unlink):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = self.data.snap_tgt_session
|
||||
source = session.get('source_vol_id')
|
||||
target = session.get('target_vol_id')
|
||||
snap_name = session.get('snap_name')
|
||||
snap_id = session.get('snapid')
|
||||
loop = False
|
||||
is_unlinked = self.common._unlink_snapshot(session, array, extra_specs)
|
||||
mck_unlink.assert_called_once_with(
|
||||
array, target, source, snap_name, extra_specs, snap_id, loop)
|
||||
self.assertTrue(is_unlinked)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'unlink_snapvx_tgt_volume')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap',
|
||||
return_value=tpd.PowerMaxData.priv_snap_response.get(
|
||||
'snapshotSrcs')[0])
|
||||
def test_unlink_temp_snapshot_not_unlinked(self, mck_get, mck_unlink):
|
||||
array = self.data.array
|
||||
extra_specs = self.data.extra_specs
|
||||
session = self.data.snap_tgt_session
|
||||
source = session.get('source_vol_id')
|
||||
target = session.get('target_vol_id')
|
||||
snap_name = session.get('snap_name')
|
||||
snap_id = session.get('snapid')
|
||||
loop = False
|
||||
is_unlinked = self.common._unlink_snapshot(session, array, extra_specs)
|
||||
mck_unlink.assert_called_once_with(
|
||||
array, target, source, snap_name, extra_specs, snap_id, loop)
|
||||
self.assertFalse(is_unlinked)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'delete_temp_volume_snap')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap',
|
||||
return_value=dict())
|
||||
def test_delete_temp_snapshot(self, mck_get, mck_delete):
|
||||
session = self.data.snap_tgt_session
|
||||
array = self.data.array
|
||||
snap_name = session.get('snap_name')
|
||||
source = session.get('source_vol_id')
|
||||
snap_id = session.get('snapid')
|
||||
self.common._delete_temp_snapshot(session, array)
|
||||
mck_delete.assert_called_once_with(array, snap_name, source, snap_id)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'delete_temp_volume_snap')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snap',
|
||||
return_value={'linkedDevices': 'details'})
|
||||
def test_delete_temp_snapshot_is_linked(self, mck_get, mck_delete):
|
||||
session = self.data.snap_tgt_session
|
||||
array = self.data.array
|
||||
self.common._delete_temp_snapshot(session, array)
|
||||
mck_delete.assert_not_called()
|
||||
|
@ -235,9 +235,9 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
@mock.patch.object(masking.PowerMaxMasking,
|
||||
'remove_vol_from_storage_group')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_delete_from_srp')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_cleanup_replication_source(
|
||||
self, mck_sync, mock_del, mock_rm, mock_clean):
|
||||
self, mck_cleanup, mock_del, mock_rm, mock_clean):
|
||||
self.common._cleanup_replication_source(
|
||||
self.data.array, self.data.test_volume, 'vol1',
|
||||
{'device_id': self.data.device_id}, self.extra_specs)
|
||||
@ -460,10 +460,10 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_failover_replication',
|
||||
return_value=({}, {}))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_arrays_list',
|
||||
return_value=['123'])
|
||||
def test_failover_host_async(self, mck_arrays, mck_sync, mock_fg):
|
||||
def test_failover_host_async(self, mck_arrays, mck_cleanup, mock_fg):
|
||||
volumes = [self.data.test_volume]
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
extra_specs['rep_mode'] = utils.REP_ASYNC
|
||||
@ -928,8 +928,8 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
return_value=(
|
||||
tpd.PowerMaxData.provider_location_clone,
|
||||
tpd.PowerMaxData.replication_update, {}))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_clone_check')
|
||||
def test_cloned_rep_volume(self, mck_clone, mck_meta, mck_clone_chk):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
def test_cloned_rep_volume(self, mck_cleanup, mck_meta, mck_clone_chk):
|
||||
metadata = deepcopy(self.data.volume_metadata)
|
||||
metadata['BackendID'] = self.data.rep_backend_id_sync
|
||||
ref_model_update = {
|
||||
@ -1069,7 +1069,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
self.assertTrue(success)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
||||
@mock.patch.object(
|
||||
@ -1086,7 +1086,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
'remote_array': tpd.PowerMaxData.remote_array},
|
||||
tpd.PowerMaxData.rep_extra_specs, False))
|
||||
def test_migrate_volume_success_no_rep_to_rep(
|
||||
self, mck_configure, mck_retype, mck_protect, mck_get, mck_check,
|
||||
self, mck_configure, mck_retype, mck_protect, mck_get, mck_cleanup,
|
||||
mck_valid):
|
||||
self.common.rep_config = {'mode': utils.REP_SYNC,
|
||||
'array': self.data.array}
|
||||
@ -1127,6 +1127,8 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
self.data.rep_extra_specs, volume)
|
||||
self.assertTrue(success)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_volume_snapshot_list',
|
||||
return_value=list())
|
||||
@mock.patch.object(utils.PowerMaxUtils, 'get_rep_config',
|
||||
return_value=tpd.PowerMaxData.rep_config_async)
|
||||
@mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata',
|
||||
@ -1143,14 +1145,15 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
tpd.PowerMaxData.rep_info_dict,
|
||||
tpd.PowerMaxData.rep_extra_specs_mgmt,
|
||||
True))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_device_snapvx')
|
||||
@mock.patch.object(common.PowerMaxCommon, 'break_rdf_device_pair_session',
|
||||
return_value=(tpd.PowerMaxData.rep_extra_specs_mgmt,
|
||||
True))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||
def test_migrate_volume_success_rep_to_rep(
|
||||
self, mck_valid, mck_break, mck_sync, mck_rep, mck_retype,
|
||||
mck_resume, mck_slo, mck_upd_meta, mck_get_meta, mck_rep_conf):
|
||||
self, mck_valid, mck_break, mck_cleanup, mck_rep, mck_retype,
|
||||
mck_resume, mck_slo, mck_upd_meta, mck_get_meta, mck_rep_conf,
|
||||
mck_get_snaps):
|
||||
array = self.data.array
|
||||
volume = self.data.test_volume
|
||||
device_id = self.data.device_id
|
||||
@ -1179,7 +1182,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
mck_valid.assert_any_call(array, extra_specs)
|
||||
mck_break.assert_called_once_with(
|
||||
array, device_id, volume_name, extra_specs, volume)
|
||||
mck_sync.assert_called_once_with(array, device_id, extra_specs)
|
||||
mck_cleanup.assert_called_once_with(array, device_id, extra_specs)
|
||||
mck_rep.assert_called_once_with(
|
||||
array, volume, device_id, target_extra_specs)
|
||||
mck_retype.assert_called_once()
|
||||
|
@ -98,12 +98,6 @@ powermax_opts = [
|
||||
cfg.MultiOpt(utils.U4P_FAILOVER_TARGETS,
|
||||
item_type=types.Dict(),
|
||||
help='Dictionary of Unisphere failover target info.'),
|
||||
cfg.IntOpt(utils.POWERMAX_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.StrOpt(utils.POWERMAX_ARRAY,
|
||||
help='Serial number of the array to connect to.'),
|
||||
cfg.StrOpt(utils.POWERMAX_SRP,
|
||||
@ -242,8 +236,6 @@ class PowerMaxCommon(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.POWERMAX_SNAPVX_UNLINK_LIMIT)
|
||||
self.powermax_array_tag_list = self.configuration.safe_get(
|
||||
utils.POWERMAX_ARRAY_TAG_LIST)
|
||||
self.powermax_short_host_name_template = self.configuration.safe_get(
|
||||
@ -626,11 +618,7 @@ class PowerMaxCommon(object):
|
||||
source_device_id = self._find_device_on_array(
|
||||
source_volume, extra_specs)
|
||||
|
||||
if not self.next_gen and (
|
||||
extra_specs.get('rep_mode', None) == utils.REP_METRO):
|
||||
self._sync_check(array, source_device_id, extra_specs)
|
||||
else:
|
||||
self._clone_check(array, source_device_id, extra_specs)
|
||||
self._cleanup_device_snapvx(array, source_device_id, extra_specs)
|
||||
|
||||
clone_dict, rep_update, rep_info_dict = self._create_cloned_volume(
|
||||
clone_volume, source_volume, extra_specs)
|
||||
@ -1219,7 +1207,7 @@ class PowerMaxCommon(object):
|
||||
|
||||
# 2 - Check if volume is part of an on-going clone operation or if vol
|
||||
# has source snapshots but not next-gen array
|
||||
self._sync_check(array, device_id, ex_specs)
|
||||
self._cleanup_device_snapvx(array, device_id, ex_specs)
|
||||
__, snapvx_src, __ = self.rest.is_vol_in_rep_session(array, device_id)
|
||||
if snapvx_src:
|
||||
if not self.next_gen:
|
||||
@ -1946,7 +1934,7 @@ class PowerMaxCommon(object):
|
||||
|
||||
# Perform any snapvx cleanup if required before creating the clone
|
||||
if is_snapshot or from_snapvx:
|
||||
self._clone_check(array, source_device_id, extra_specs)
|
||||
self._cleanup_device_snapvx(array, source_device_id, extra_specs)
|
||||
|
||||
if not is_snapshot:
|
||||
clone_dict, rep_update, rep_info_dict = self._create_replica(
|
||||
@ -2047,14 +2035,8 @@ class PowerMaxCommon(object):
|
||||
:param volume: volume object to be deleted
|
||||
:returns: volume_name (string vol name)
|
||||
"""
|
||||
source_device_id = None
|
||||
volume_name = volume.name
|
||||
extra_specs = self._initial_setup(volume)
|
||||
prov_loc = volume.provider_location
|
||||
|
||||
if isinstance(prov_loc, six.string_types):
|
||||
name = ast.literal_eval(prov_loc)
|
||||
source_device_id = name.get('source_device_id')
|
||||
|
||||
device_id = self._find_device_on_array(volume, extra_specs)
|
||||
if device_id is None:
|
||||
@ -2069,8 +2051,27 @@ class PowerMaxCommon(object):
|
||||
|
||||
# Check if the volume being deleted is a
|
||||
# source or target for copy session
|
||||
self._sync_check(array, device_id, extra_specs,
|
||||
source_device_id=source_device_id)
|
||||
self._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
# Confirm volume has no more snapshots associated and is not a target
|
||||
snapshots = self.rest.get_volume_snapshot_list(array, device_id)
|
||||
__, snapvx_target_details = self.rest.find_snap_vx_sessions(
|
||||
array, device_id, tgt_only=True)
|
||||
if snapshots:
|
||||
snapshot_names = ', '.join(
|
||||
snap.get('snapshotName') for snap in snapshots)
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Cannot delete device %s as it currently has the following '
|
||||
'active snapshots: %s. Please try again once these snapshots '
|
||||
'are no longer active.') % (device_id, snapshot_names))
|
||||
if snapvx_target_details:
|
||||
source_device = snapvx_target_details.get('source_vol_id')
|
||||
snapshot_name = snapvx_target_details.get('snap_name')
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Cannot delete device %s as it is currently a linked target '
|
||||
'of snapshot %s. The source device of this link is %s. '
|
||||
'Please try again once this snapshots is no longer '
|
||||
'active.') % (device_id, snapshot_name, source_device))
|
||||
|
||||
# Remove from any storage groups and cleanup replication
|
||||
self._remove_vol_and_cleanup_replication(
|
||||
array, device_id, volume_name, extra_specs, volume)
|
||||
@ -2910,111 +2911,6 @@ class PowerMaxCommon(object):
|
||||
self._delete_from_srp(
|
||||
array, target_device_id, clone_name, extra_specs)
|
||||
|
||||
@retry(retry_exc_tuple, interval=1, retries=3)
|
||||
def _sync_check(self, array, device_id, extra_specs,
|
||||
tgt_only=False, source_device_id=None):
|
||||
"""Check if volume is part of a SnapVx sync process.
|
||||
|
||||
:param array: the array serial number
|
||||
:param device_id: volume instance
|
||||
:param tgt_only: Flag - return only sessions where device is target
|
||||
:param extra_specs: extra specifications
|
||||
:param tgt_only: Flag to specify if it is a target
|
||||
:param source_device_id: source_device_id if it has one
|
||||
"""
|
||||
if not source_device_id and tgt_only:
|
||||
source_device_id = self._get_target_source_device(
|
||||
array, device_id)
|
||||
if source_device_id:
|
||||
@coordination.synchronized("emc-source-{src_device_id}")
|
||||
def do_unlink_and_delete_snap(src_device_id):
|
||||
LOG.debug("Locking on source device %(device_id)s.",
|
||||
{'device_id': src_device_id})
|
||||
self._do_sync_check(
|
||||
array, device_id, extra_specs, tgt_only)
|
||||
|
||||
do_unlink_and_delete_snap(source_device_id)
|
||||
else:
|
||||
self._do_sync_check(
|
||||
array, device_id, extra_specs, tgt_only)
|
||||
|
||||
def _do_sync_check(
|
||||
self, array, device_id, extra_specs, tgt_only=False):
|
||||
"""Check if volume is part of a SnapVx sync process.
|
||||
|
||||
:param array: the array serial number
|
||||
:param device_id: volume instance
|
||||
:param tgt_only: Flag - return only sessions where device is target
|
||||
:param extra_specs: extra specifications
|
||||
:param tgt_only: Flag to specify if it is a target
|
||||
"""
|
||||
snapvx_tgt, snapvx_src, __ = self.rest.is_vol_in_rep_session(
|
||||
array, device_id)
|
||||
get_sessions = True if snapvx_tgt or snapvx_src else False
|
||||
|
||||
if get_sessions:
|
||||
src_sessions, tgt_session = self.rest.find_snap_vx_sessions(
|
||||
array, device_id, tgt_only)
|
||||
if tgt_session:
|
||||
self._unlink_targets_and_delete_temp_snapvx(
|
||||
tgt_session, array, extra_specs)
|
||||
if src_sessions and not tgt_only:
|
||||
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)
|
||||
|
||||
def _unlink_targets_and_delete_temp_snapvx(
|
||||
self, session, array, extra_specs):
|
||||
"""Unlink target and delete the temporary snapvx if valid candidate.
|
||||
|
||||
:param session: the snapvx session
|
||||
:param array: the array serial number
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
snap_name = session.get('snap_name')
|
||||
if not snap_name:
|
||||
msg = _("Snap_name not present in volume's snapvx session. "
|
||||
"Unable to perform unlink and cleanup.")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(msg)
|
||||
source = session.get('source_vol_id')
|
||||
snap_id = session.get('snapid')
|
||||
is_legacy = 'EMC_SMI' in snap_name
|
||||
is_temp = utils.CLONE_SNAPSHOT_NAME in snap_name
|
||||
|
||||
target, cm_enabled = None, False
|
||||
if session.get('target_vol_id'):
|
||||
target = session.get('target_vol_id')
|
||||
cm_enabled = session.get('copy_mode')
|
||||
|
||||
if target and snap_name:
|
||||
loop = True if cm_enabled else False
|
||||
LOG.debug(
|
||||
"Unlinking source from target. Source: %(vol)s, Target: "
|
||||
"%(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, snap_id,
|
||||
loop)
|
||||
|
||||
# Candidates for deletion:
|
||||
# 1. If legacy snapshot with 'EMC_SMI' in snapshot name
|
||||
# 2. If snapVX snapshot is temporary
|
||||
# If a target is present in the snap session it will be unlinked by
|
||||
# the provision.unlink_snapvx_tgt_volume call above. This accounts
|
||||
# for both CM enabled and disabled, as such by this point there will
|
||||
# either be no target or it would have been unlinked, therefore
|
||||
# delete if it is legacy or temp.
|
||||
if is_legacy or is_temp:
|
||||
LOG.debug(
|
||||
"Deleting temporary snapshot. Source: %(vol)s, snap name: "
|
||||
"%(name)s, snap id: %(snapid)s.", {
|
||||
'vol': source, 'name': snap_name, 'snapid': snap_id})
|
||||
self.provision.delete_temp_volume_snap(
|
||||
array, snap_name, source, snap_id)
|
||||
|
||||
def _get_target_source_device(self, array, device_id):
|
||||
"""Get the source device id of the target.
|
||||
|
||||
@ -3036,14 +2932,14 @@ class PowerMaxCommon(object):
|
||||
|
||||
return source_device_id
|
||||
|
||||
def _clone_check(
|
||||
self, array, device_id, extra_specs, force_unlink=False):
|
||||
@retry(retry_exc_tuple, interval=1, retries=3)
|
||||
def _cleanup_device_snapvx(
|
||||
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
|
||||
:param force_unlink: force unlink even if not expired
|
||||
"""
|
||||
snapvx_tgt, snapvx_src, __ = self.rest.is_vol_in_rep_session(
|
||||
array, device_id)
|
||||
@ -3053,47 +2949,109 @@ class PowerMaxCommon(object):
|
||||
def do_unlink_and_delete_snap(src_device_id):
|
||||
src_sessions, tgt_session = self.rest.find_snap_vx_sessions(
|
||||
array, src_device_id)
|
||||
count = 0
|
||||
if tgt_session and count < self.snapvx_unlink_limit:
|
||||
self._delete_valid_snapshot(
|
||||
array, tgt_session, extra_specs, force_unlink)
|
||||
count += 1
|
||||
if tgt_session:
|
||||
self._unlink_and_delete_temporary_snapshots(
|
||||
tgt_session, array, extra_specs)
|
||||
if src_sessions:
|
||||
for session in src_sessions:
|
||||
if count < self.snapvx_unlink_limit:
|
||||
self._delete_valid_snapshot(
|
||||
array, session, extra_specs, force_unlink)
|
||||
count += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if not self.rest.is_snap_id:
|
||||
src_sessions.sort(
|
||||
key=lambda k: k['snapid'], reverse=True)
|
||||
for src_session in src_sessions:
|
||||
self._unlink_and_delete_temporary_snapshots(
|
||||
src_session, array, extra_specs)
|
||||
do_unlink_and_delete_snap(device_id)
|
||||
|
||||
def _delete_valid_snapshot(
|
||||
self, array, session, extra_specs, force_unlink=False):
|
||||
"""Delete a snapshot if valid candidate for deletion.
|
||||
def _unlink_and_delete_temporary_snapshots(
|
||||
self, session, array, extra_specs):
|
||||
"""Helper for unlinking and deleting temporary snapshot sessions
|
||||
|
||||
:param array: the array serial
|
||||
:param session: the snapvx session
|
||||
:param session: snapvx session
|
||||
:param array: the array serial number
|
||||
:param extra_specs: extra specifications
|
||||
:param force_unlink: force unlink even if not expired
|
||||
"""
|
||||
is_legacy = 'EMC_SMI' in session['snap_name']
|
||||
is_temp = utils.CLONE_SNAPSHOT_NAME in session['snap_name']
|
||||
is_expired = session['expired']
|
||||
if force_unlink:
|
||||
is_expired = True
|
||||
is_valid = True if is_legacy or (is_temp and is_expired) else False
|
||||
if is_valid:
|
||||
try:
|
||||
self._unlink_targets_and_delete_temp_snapvx(
|
||||
session, array, extra_specs)
|
||||
except exception.VolumeBackendAPIException as e:
|
||||
# Ignore and continue as snapshot has been unlinked
|
||||
# successfully with incorrect status code returned
|
||||
if ('404' and session['snap_name'] and
|
||||
'does not exist' in six.text_type(e)):
|
||||
pass
|
||||
try:
|
||||
session_unlinked = self._unlink_snapshot(
|
||||
session, array, extra_specs)
|
||||
if session_unlinked:
|
||||
self._delete_temp_snapshot(session, array)
|
||||
except exception.VolumeBackendAPIException as e:
|
||||
# Ignore and continue as snapshot has been unlinked
|
||||
# successfully with incorrect status code returned
|
||||
if ('404' and session['snap_name'] and
|
||||
'does not exist' in six.text_type(e)):
|
||||
pass
|
||||
|
||||
def _unlink_snapshot(self, session, array, extra_specs):
|
||||
"""Helper for unlinking temporary snapshot during cleanup.
|
||||
|
||||
:param session: session that contains snapshot
|
||||
:param array: the array serial number
|
||||
:param extra_specs: extra specifications
|
||||
:return:
|
||||
"""
|
||||
snap_name = session.get('snap_name')
|
||||
source = session.get('source_vol_id')
|
||||
snap_id = session.get('snapid')
|
||||
|
||||
snap_info = self.rest.get_volume_snap(
|
||||
array, source, snap_name, snap_id)
|
||||
is_linked = snap_info.get('linkedDevices')
|
||||
|
||||
target, cm_enabled = None, False
|
||||
if session.get('target_vol_id'):
|
||||
target = session.get('target_vol_id')
|
||||
cm_enabled = session.get('copy_mode')
|
||||
|
||||
if target and snap_name and is_linked:
|
||||
loop = True if cm_enabled else False
|
||||
LOG.debug(
|
||||
"Unlinking source from target. Source: %(vol)s, Target: "
|
||||
"%(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, snap_id,
|
||||
loop)
|
||||
|
||||
is_unlinked = True
|
||||
snap_info = self.rest.get_volume_snap(
|
||||
array, source, snap_name, snap_id)
|
||||
if snap_info and snap_info.get('linkedDevices'):
|
||||
is_unlinked = False
|
||||
return is_unlinked
|
||||
|
||||
def _delete_temp_snapshot(self, session, array):
|
||||
"""Helper for deleting temporary snapshot during cleanup.
|
||||
|
||||
:param session: Session that contains snapshot
|
||||
:param array: the array serial number
|
||||
"""
|
||||
snap_name = session.get('snap_name')
|
||||
source = session.get('source_vol_id')
|
||||
snap_id = session.get('snapid')
|
||||
LOG.debug(
|
||||
"Deleting temp snapshot if it exists. Snap name is: "
|
||||
"%(snap_name)s, Source is: %(source)s, "
|
||||
"Snap id: %(snap_id)s.",
|
||||
{'snap_name': snap_name, 'source': source,
|
||||
'snap_id': snap_id})
|
||||
is_legacy = 'EMC_SMI' in snap_name if snap_name else False
|
||||
is_temp = (
|
||||
utils.CLONE_SNAPSHOT_NAME in snap_name if snap_name else False)
|
||||
snap_info = self.rest.get_volume_snap(
|
||||
array, source, snap_name, snap_id)
|
||||
is_linked = snap_info.get('linkedDevices') if snap_info else False
|
||||
|
||||
# Candidates for deletion:
|
||||
# 1. If legacy snapshot with 'EMC_SMI' in snapshot name
|
||||
# 2. If snapVX snapshot is temporary
|
||||
# 3. Snapshot is unlinked. Call _unlink_snapshot before delete.
|
||||
if (is_legacy or is_temp) and not is_linked:
|
||||
LOG.debug(
|
||||
"Deleting temporary snapshot. Source: %(vol)s, snap name: "
|
||||
"%(name)s, snap id: %(snapid)s.", {
|
||||
'vol': source, 'name': snap_name, 'snapid': snap_id})
|
||||
self.provision.delete_temp_volume_snap(
|
||||
array, snap_name, source, snap_id)
|
||||
|
||||
def manage_existing(self, volume, external_ref):
|
||||
"""Manages an existing PowerMax/VMAX Volume (import to Cinder).
|
||||
@ -3367,7 +3325,7 @@ class PowerMaxCommon(object):
|
||||
{'id': volume_id})
|
||||
else:
|
||||
# Check if volume is snap source
|
||||
self._clone_check(array, device_id, extra_specs)
|
||||
self._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
snapvx_tgt, snapvx_src, __ = self.rest.is_vol_in_rep_session(
|
||||
array, device_id)
|
||||
if snapvx_src or snapvx_tgt:
|
||||
@ -3933,13 +3891,7 @@ class PowerMaxCommon(object):
|
||||
:param extra_specs: the extra specifications
|
||||
:returns: bool
|
||||
"""
|
||||
model_update, success = None, False
|
||||
rep_info_dict, rep_extra_specs, rep_mode, rep_status = (
|
||||
None, None, None, False)
|
||||
resume_target_sg, resume_original_sg = False, False
|
||||
resume_original_sg_dict = dict()
|
||||
orig_mgmt_sg_name = ''
|
||||
is_partitioned = False
|
||||
orig_mgmt_sg_name = None
|
||||
|
||||
target_extra_specs = dict(new_type['extra_specs'])
|
||||
target_extra_specs.update({
|
||||
@ -3952,30 +3904,10 @@ class PowerMaxCommon(object):
|
||||
target_extra_specs)
|
||||
target_extra_specs.update(
|
||||
{utils.DISABLECOMPRESSION: compression_disabled})
|
||||
|
||||
was_rep_enabled = self.utils.is_replication_enabled(extra_specs)
|
||||
if self.utils.is_replication_enabled(target_extra_specs):
|
||||
target_backend_id = target_extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
target_rep_config = self.utils.get_rep_config(
|
||||
target_backend_id, self.rep_configs)
|
||||
rep_mode = target_rep_config['mode']
|
||||
target_extra_specs[utils.REP_MODE] = rep_mode
|
||||
target_extra_specs[utils.REP_CONFIG] = target_rep_config
|
||||
is_rep_enabled = True
|
||||
else:
|
||||
is_rep_enabled = False
|
||||
|
||||
backend_ids_differ = False
|
||||
if was_rep_enabled and is_rep_enabled:
|
||||
curr_backend_id = extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
tgt_backend_id = target_extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
backend_ids_differ = curr_backend_id != tgt_backend_id
|
||||
(was_rep_enabled, is_rep_enabled, backend_ids_differ, rep_mode,
|
||||
target_extra_specs) = (
|
||||
self._get_replication_flags(
|
||||
extra_specs, target_extra_specs))
|
||||
|
||||
if was_rep_enabled and not self.promotion:
|
||||
self._validate_rdfg_status(array, extra_specs)
|
||||
@ -3992,51 +3924,26 @@ class PowerMaxCommon(object):
|
||||
rdf_pair_broken, rdf_pair_created, vol_retyped, remote_retyped = (
|
||||
False, False, False, False)
|
||||
|
||||
self._perform_snapshot_cleanup(
|
||||
array, device_id, was_rep_enabled, is_rep_enabled,
|
||||
backend_ids_differ, extra_specs, target_extra_specs)
|
||||
|
||||
try:
|
||||
# Scenario 1: Rep -> Non-Rep
|
||||
# Scenario 2: Cleanup for Rep -> Diff Rep type
|
||||
if (was_rep_enabled and not is_rep_enabled) or backend_ids_differ:
|
||||
if self.promotion:
|
||||
resume_original_sg = False
|
||||
rdf_group = extra_specs['rdf_group_no']
|
||||
is_partitioned = self._rdf_vols_partitioned(
|
||||
array, [volume], rdf_group)
|
||||
if not is_partitioned:
|
||||
self.break_rdf_device_pair_session_promotion(
|
||||
array, device_id, volume_name, extra_specs)
|
||||
else:
|
||||
rep_extra_specs, resume_original_sg = (
|
||||
self.break_rdf_device_pair_session(
|
||||
array, device_id, volume_name, extra_specs,
|
||||
volume))
|
||||
status = (REPLICATION_ERROR if self.promotion else
|
||||
REPLICATION_DISABLED)
|
||||
model_update = {
|
||||
'replication_status': status,
|
||||
'replication_driver_data': None}
|
||||
rdf_pair_broken = True
|
||||
if resume_original_sg:
|
||||
resume_original_sg_dict = {
|
||||
utils.ARRAY: array,
|
||||
utils.SG_NAME: rep_extra_specs['mgmt_sg_name'],
|
||||
utils.RDF_GROUP_NO: rep_extra_specs['rdf_group_no'],
|
||||
utils.EXTRA_SPECS: rep_extra_specs}
|
||||
(model_update, resume_original_sg_dict, rdf_pair_broken,
|
||||
resume_original_sg, is_partitioned) = (
|
||||
self._prep_rep_to_non_rep(
|
||||
array, device_id, volume_name, volume, was_rep_enabled,
|
||||
is_rep_enabled, backend_ids_differ, extra_specs))
|
||||
|
||||
# Scenario 1: Non-Rep -> Rep
|
||||
# Scenario 2: Rep -> Diff Rep type
|
||||
if (not was_rep_enabled and is_rep_enabled) or backend_ids_differ:
|
||||
self._sync_check(array, device_id, extra_specs)
|
||||
(rep_status, rep_driver_data, rep_info_dict,
|
||||
rep_extra_specs, resume_target_sg) = (
|
||||
self.configure_volume_replication(
|
||||
array, volume, device_id, target_extra_specs))
|
||||
if rep_status != 'first_vol_in_rdf_group':
|
||||
rdf_pair_created = True
|
||||
model_update = {
|
||||
'replication_status': rep_status,
|
||||
'replication_driver_data': six.text_type(
|
||||
{'device_id': rep_info_dict['target_device_id'],
|
||||
'array': rep_info_dict['remote_array']})}
|
||||
(model_update, rdf_pair_created, rep_status, rep_driver_data,
|
||||
rep_info_dict, rep_extra_specs, resume_target_sg) = (
|
||||
self._prep_non_rep_to_rep(
|
||||
array, device_id, volume, was_rep_enabled,
|
||||
is_rep_enabled, backend_ids_differ, target_extra_specs))
|
||||
|
||||
success, target_sg_name = self._retype_volume(
|
||||
array, srp, device_id, volume, volume_name, extra_specs,
|
||||
@ -4128,6 +4035,185 @@ class PowerMaxCommon(object):
|
||||
finally:
|
||||
raise e
|
||||
|
||||
def _get_replication_flags(self, extra_specs, target_extra_specs):
|
||||
"""Get replication flags from extra specifications.
|
||||
|
||||
:param extra_specs: extra specification -- dict
|
||||
:param target_extra_specs: target extra specification -- dict
|
||||
:returns: was_rep_enabled -- bool, is_rep_enabled -- bool,
|
||||
backend_ids_differ -- bool, rep_mode -- str,
|
||||
target_extra_specs -- dict
|
||||
"""
|
||||
rep_mode = None
|
||||
was_rep_enabled = self.utils.is_replication_enabled(extra_specs)
|
||||
if self.utils.is_replication_enabled(target_extra_specs):
|
||||
target_backend_id = target_extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
target_rep_config = self.utils.get_rep_config(
|
||||
target_backend_id, self.rep_configs)
|
||||
rep_mode = target_rep_config['mode']
|
||||
target_extra_specs[utils.REP_MODE] = rep_mode
|
||||
target_extra_specs[utils.REP_CONFIG] = target_rep_config
|
||||
is_rep_enabled = True
|
||||
else:
|
||||
is_rep_enabled = False
|
||||
|
||||
backend_ids_differ = False
|
||||
if was_rep_enabled and is_rep_enabled:
|
||||
curr_backend_id = extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
tgt_backend_id = target_extra_specs.get(
|
||||
utils.REPLICATION_DEVICE_BACKEND_ID,
|
||||
utils.BACKEND_ID_LEGACY_REP)
|
||||
backend_ids_differ = curr_backend_id != tgt_backend_id
|
||||
|
||||
return (was_rep_enabled, is_rep_enabled, backend_ids_differ, rep_mode,
|
||||
target_extra_specs)
|
||||
|
||||
def _prep_non_rep_to_rep(
|
||||
self, array, device_id, volume, was_rep_enabled,
|
||||
is_rep_enabled, backend_ids_differ, target_extra_specs):
|
||||
"""Prepare for non rep to rep retype.
|
||||
|
||||
:param array: the array serial number -- str
|
||||
:param device_id: the device id -- str
|
||||
:param volume: the volume object -- objects.Volume
|
||||
:param was_rep_enabled: flag -- bool
|
||||
:param is_rep_enabled: flag -- bool
|
||||
:param backend_ids_differ: flag -- bool
|
||||
:param target_extra_specs: target extra specs -- dict
|
||||
:returns: model_update -- dict, rdf_pair_created -- bool,
|
||||
rep_status -- str, rep_driver_data -- dict,
|
||||
rep_info_dict -- dict, rep_extra_specs -- dict,
|
||||
resume_target_sg -- bool
|
||||
"""
|
||||
model_update, rep_status = None, None
|
||||
resume_target_sg = False
|
||||
rdf_pair_created = False
|
||||
rep_driver_data, rep_info_dict = dict(), dict()
|
||||
rep_extra_specs = dict()
|
||||
if (not was_rep_enabled and is_rep_enabled) or backend_ids_differ:
|
||||
(rep_status, rep_driver_data, rep_info_dict,
|
||||
rep_extra_specs, resume_target_sg) = (
|
||||
self.configure_volume_replication(
|
||||
array, volume, device_id, target_extra_specs))
|
||||
if rep_status != 'first_vol_in_rdf_group':
|
||||
rdf_pair_created = True
|
||||
model_update = {
|
||||
'replication_status': rep_status,
|
||||
'replication_driver_data': six.text_type(
|
||||
{'device_id': rep_info_dict['target_device_id'],
|
||||
'array': rep_info_dict['remote_array']})}
|
||||
|
||||
return (model_update, rdf_pair_created, rep_status, rep_driver_data,
|
||||
rep_info_dict, rep_extra_specs, resume_target_sg)
|
||||
|
||||
def _prep_rep_to_non_rep(
|
||||
self, array, device_id, volume_name, volume, was_rep_enabled,
|
||||
is_rep_enabled, backend_ids_differ, extra_specs):
|
||||
"""Preparation for replication to non-replicated.
|
||||
|
||||
:param array: the array serial number -- str
|
||||
:param device_id: device_id: the device id -- str
|
||||
:param volume_name: the volume name -- str
|
||||
:param volume: the volume object -- objects.Volume
|
||||
:param was_rep_enabled: flag -- bool
|
||||
:param is_rep_enabled: flag -- bool
|
||||
:param backend_ids_differ: flag -- bool
|
||||
:param extra_specs: extra specs -- dict
|
||||
:returns: model_update --dict , resume_original_sg_dict -- dict,
|
||||
rdf_pair_broken -- bool, resume_original_sg -- bool,
|
||||
is_partitioned -- bool
|
||||
"""
|
||||
model_update = dict()
|
||||
resume_original_sg_dict = dict()
|
||||
rdf_pair_broken = False
|
||||
resume_original_sg = False
|
||||
is_partitioned = False
|
||||
if (was_rep_enabled and not is_rep_enabled) or backend_ids_differ:
|
||||
if self.promotion:
|
||||
resume_original_sg = False
|
||||
rdf_group = extra_specs['rdf_group_no']
|
||||
is_partitioned = self._rdf_vols_partitioned(
|
||||
array, [volume], rdf_group)
|
||||
if not is_partitioned:
|
||||
self.break_rdf_device_pair_session_promotion(
|
||||
array, device_id, volume_name, extra_specs)
|
||||
else:
|
||||
rep_extra_specs, resume_original_sg = (
|
||||
self.break_rdf_device_pair_session(
|
||||
array, device_id, volume_name, extra_specs,
|
||||
volume))
|
||||
status = (REPLICATION_ERROR if self.promotion else
|
||||
REPLICATION_DISABLED)
|
||||
model_update = {
|
||||
'replication_status': status,
|
||||
'replication_driver_data': None}
|
||||
rdf_pair_broken = True
|
||||
if resume_original_sg:
|
||||
resume_original_sg_dict = {
|
||||
utils.ARRAY: array,
|
||||
utils.SG_NAME: rep_extra_specs['mgmt_sg_name'],
|
||||
utils.RDF_GROUP_NO: rep_extra_specs['rdf_group_no'],
|
||||
utils.EXTRA_SPECS: rep_extra_specs}
|
||||
return (model_update, resume_original_sg_dict, rdf_pair_broken,
|
||||
resume_original_sg, is_partitioned)
|
||||
|
||||
def _perform_snapshot_cleanup(
|
||||
self, array, device_id, was_rep_enabled, is_rep_enabled,
|
||||
backend_ids_differ, extra_specs, target_extra_specs):
|
||||
"""Perform snapshot cleanup.
|
||||
|
||||
Perform snapshot cleanup before any other changes. If retyping
|
||||
to either async or metro then there should be no linked snapshots
|
||||
on the volume.
|
||||
:param array: the array serial number -- str
|
||||
:param device_id: device_id: the device id -- str
|
||||
:param was_rep_enabled: flag -- bool
|
||||
:param is_rep_enabled: flag -- bool
|
||||
:param backend_ids_differ: flag -- bool
|
||||
:param extra_specs: extra specs -- dict
|
||||
:param target_extra_specs: target extra specs -- dict
|
||||
"""
|
||||
if (not was_rep_enabled and is_rep_enabled) or backend_ids_differ:
|
||||
target_rep_mode = target_extra_specs.get(utils.REP_MODE)
|
||||
target_is_async = target_rep_mode == utils.REP_ASYNC
|
||||
target_is_metro = target_rep_mode == utils.REP_METRO
|
||||
if target_is_async or target_is_metro:
|
||||
self._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
snapshots = self.rest.get_volume_snapshot_list(
|
||||
array, device_id)
|
||||
__, snapvx_target_details = self.rest.find_snap_vx_sessions(
|
||||
array, device_id, tgt_only=True)
|
||||
|
||||
linked_snapshots = list()
|
||||
for snapshot in snapshots:
|
||||
linked_devices = snapshot.get('linkedDevices')
|
||||
if linked_devices:
|
||||
snapshot_name = snapshot.get('snapshotName')
|
||||
linked_snapshots.append(snapshot_name)
|
||||
if linked_snapshots:
|
||||
snapshot_names = ', '.join(linked_snapshots)
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Unable to complete retype as volume has active'
|
||||
'snapvx links. Cannot retype to Asynchronous or '
|
||||
'Metro modes while the volume has active links. '
|
||||
'Please wait until these snapvx operations have '
|
||||
'completed and try again. Snapshots: '
|
||||
'%s') % snapshot_names)
|
||||
if snapvx_target_details:
|
||||
source_vol_id = snapvx_target_details.get('source_vol_id')
|
||||
snap_name = snapvx_target_details.get('snap_name')
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Unable to complete retype as volume is a snapvx '
|
||||
'target. Cannot retype to Asynchronous or Metro '
|
||||
'modes in this state. Please wait until these snapvx '
|
||||
'operations complete and try again. Volume %s is '
|
||||
'currently a target of snapshot %s with source device '
|
||||
'%s') % (device_id, snap_name, source_vol_id))
|
||||
|
||||
def _cleanup_on_migrate_failure(
|
||||
self, rdf_pair_broken, rdf_pair_created, vol_retyped,
|
||||
remote_retyped, extra_specs, target_extra_specs, volume,
|
||||
@ -5168,7 +5254,7 @@ class PowerMaxCommon(object):
|
||||
{'sourceName': volume_name})
|
||||
device_id = volume_dict['device_id']
|
||||
# Check if volume is snap target (e.g. if clone volume)
|
||||
self._sync_check(array, device_id, extra_specs)
|
||||
self._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
# Remove from any storage groups and cleanup replication
|
||||
self._remove_vol_and_cleanup_replication(
|
||||
array, device_id, volume_name, extra_specs, volume)
|
||||
@ -5571,6 +5657,61 @@ class PowerMaxCommon(object):
|
||||
array, vol_grp_name)
|
||||
deleted_volume_device_ids = []
|
||||
|
||||
# If volumes are being deleted along with the group, ensure snapshot
|
||||
# cleanup completes before doing any replication/storage group cleanup.
|
||||
remaining_device_snapshots = list()
|
||||
remaining_snapvx_targets = list()
|
||||
for vol in volumes:
|
||||
extra_specs = self._initial_setup(vol)
|
||||
device_id = self._find_device_on_array(vol, extra_specs)
|
||||
self._cleanup_device_snapvx(array, device_id, extra_specs)
|
||||
snapshots = self.rest.get_volume_snapshot_list(array, device_id)
|
||||
__, snapvx_target_details = self.rest.find_snap_vx_sessions(
|
||||
array, device_id, tgt_only=True)
|
||||
if snapshots:
|
||||
snapshot_names = ', '.join(
|
||||
snap.get('snapshotName') for snap in snapshots)
|
||||
snap_details = {
|
||||
'device_id': device_id, 'snapshot_names': snapshot_names}
|
||||
remaining_device_snapshots.append(snap_details)
|
||||
if snapvx_target_details:
|
||||
source_vol_id = snapvx_target_details.get('source_vol_id')
|
||||
snap_name = snapvx_target_details.get('snap_name')
|
||||
target_details = {
|
||||
'device_id': device_id, 'source_vol_id': source_vol_id,
|
||||
'snapshot_name': snap_name}
|
||||
remaining_snapvx_targets.append(target_details)
|
||||
|
||||
# Fail out if volumes to be deleted still have snapshots.
|
||||
if remaining_device_snapshots:
|
||||
for details in remaining_device_snapshots:
|
||||
device_id = details.get('device_id')
|
||||
snapshot_names = details.get('snapshot_names')
|
||||
LOG.error('Cannot delete device %s, it has the '
|
||||
'following active snapshots, %s.',
|
||||
device_id, snapshot_names)
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Group volumes have active snapshots. Cannot perform group '
|
||||
'delete. Wait for snapvx sessions to complete their '
|
||||
'processes or remove volumes from group before attempting '
|
||||
'to delete again. Please see previously logged error '
|
||||
'message for device and snapshot details.'))
|
||||
|
||||
if remaining_snapvx_targets:
|
||||
for details in remaining_snapvx_targets:
|
||||
device_id = details.get('device_id')
|
||||
snap_name = details.get('snapshot_name')
|
||||
source_vol_id = details.get('source_vol_id')
|
||||
LOG.error('Cannot delete device %s, it is current a target '
|
||||
'of snapshot %s with source device id %s',
|
||||
device_id, snap_name, source_vol_id)
|
||||
raise exception.VolumeBackendAPIException(_(
|
||||
'Some group volumes are targets of a snapvx session. Cannot '
|
||||
'perform group delete. Wait for snapvx sessions to complete '
|
||||
'their processes or remove volumes from group before '
|
||||
'attempting to delete again. Please see previously logged '
|
||||
'error message for device and snapshot details.'))
|
||||
|
||||
# Remove replication for group, if applicable
|
||||
if group.is_replicated:
|
||||
vt_extra_specs = self.utils.get_volumetype_extra_specs(
|
||||
@ -5591,11 +5732,8 @@ class PowerMaxCommon(object):
|
||||
interval_retries_dict)
|
||||
for vol in volumes:
|
||||
extra_specs = self._initial_setup(vol)
|
||||
device_id = self._find_device_on_array(
|
||||
vol, extra_specs)
|
||||
device_id = self._find_device_on_array(vol, extra_specs)
|
||||
if device_id in volume_device_ids:
|
||||
self._clone_check(
|
||||
array, device_id, extra_specs, force_unlink=True)
|
||||
self.masking.remove_and_reset_members(
|
||||
array, vol, device_id, vol.name,
|
||||
extra_specs, False)
|
||||
@ -6706,7 +6844,7 @@ class PowerMaxCommon(object):
|
||||
message=exception_message)
|
||||
else:
|
||||
snap_id = snap_id_list[0]
|
||||
self._clone_check(array, sourcedevice_id, extra_specs)
|
||||
self._cleanup_device_snapvx(array, sourcedevice_id, extra_specs)
|
||||
try:
|
||||
LOG.info("Reverting device: %(deviceid)s "
|
||||
"to snapshot: %(snapname)s.",
|
||||
|
@ -132,7 +132,6 @@ POWERMAX_ARRAY = 'powermax_array'
|
||||
POWERMAX_SRP = 'powermax_srp'
|
||||
POWERMAX_SERVICE_LEVEL = 'powermax_service_level'
|
||||
POWERMAX_PORT_GROUPS = 'powermax_port_groups'
|
||||
POWERMAX_SNAPVX_UNLINK_LIMIT = 'powermax_snapvx_unlink_limit'
|
||||
POWERMAX_ARRAY_TAG_LIST = 'powermax_array_tag_list'
|
||||
POWERMAX_SHORT_HOST_NAME_TEMPLATE = 'powermax_short_host_name_template'
|
||||
POWERMAX_PORT_GROUP_NAME_TEMPLATE = 'powermax_port_group_name_template'
|
||||
|
Loading…
Reference in New Issue
Block a user