PowerMax Driver - RDF status validation
Adding validation calls for the state of RDF enabled storage groups before create/delete/retype of volumes. Change-Id: I2fa916aaa432b5d5a28d5315444227bf2a2da631
This commit is contained in:
parent
99c09bddce
commit
5eaa924349
@ -706,6 +706,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
self.data.test_clone_volume, self.data.test_volume)
|
self.data.test_clone_volume, self.data.test_volume)
|
||||||
self.assertEqual(ref_model_update, model_update)
|
self.assertEqual(ref_model_update, model_update)
|
||||||
|
|
||||||
|
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.PowerMaxCommon, '_add_volume_to_rdf_management_group')
|
common.PowerMaxCommon, '_add_volume_to_rdf_management_group')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
@ -727,12 +728,13 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
return_value=(True, True))
|
return_value=(True, True))
|
||||||
def test_create_volume_rep_enabled(
|
def test_create_volume_rep_enabled(
|
||||||
self, mck_slo, mck_prep, mck_get, mck_create, mck_protect, mck_set,
|
self, mck_slo, mck_prep, mck_get, mck_create, mck_protect, mck_set,
|
||||||
mck_add):
|
mck_add, mck_valid):
|
||||||
volume = self.data.test_volume
|
volume = self.data.test_volume
|
||||||
volume_name = self.data.volume_id
|
volume_name = self.data.volume_id
|
||||||
volume_size = 1
|
volume_size = 1
|
||||||
extra_specs = deepcopy(self.data.rep_extra_specs)
|
extra_specs = deepcopy(self.data.rep_extra_specs)
|
||||||
extra_specs['mode'] = utils.REP_ASYNC
|
extra_specs['mode'] = utils.REP_ASYNC
|
||||||
|
extra_specs[utils.REP_CONFIG] = self.data.rep_config_async
|
||||||
volume_dict, rep_update, rep_info_dict = self.common._create_volume(
|
volume_dict, rep_update, rep_info_dict = self.common._create_volume(
|
||||||
volume, volume_name, volume_size, extra_specs)
|
volume, volume_name, volume_size, extra_specs)
|
||||||
self.assertEqual(self.data.provider_location, volume_dict)
|
self.assertEqual(self.data.provider_location, volume_dict)
|
||||||
@ -789,6 +791,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
mock_rm.assert_called_once_with(
|
mock_rm.assert_called_once_with(
|
||||||
array, volume, device_id, volume_name, ref_extra_specs, False)
|
array, volume, device_id, volume_name, ref_extra_specs, False)
|
||||||
|
|
||||||
|
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
||||||
@mock.patch.object(rest.PowerMaxRest, 'srdf_resume_replication')
|
@mock.patch.object(rest.PowerMaxRest, 'srdf_resume_replication')
|
||||||
@ -800,7 +803,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
return_value=({'mgmt_sg_name': tpd.PowerMaxData.rdf_managed_async_grp,
|
return_value=({'mgmt_sg_name': tpd.PowerMaxData.rdf_managed_async_grp,
|
||||||
'rdf_group_no': tpd.PowerMaxData.rdf_group_no_1}, True))
|
'rdf_group_no': tpd.PowerMaxData.rdf_group_no_1}, True))
|
||||||
def test_migrate_volume_success_rep_to_no_rep(
|
def test_migrate_volume_success_rep_to_no_rep(
|
||||||
self, mck_break, mck_retype, mck_resume, mck_get):
|
self, mck_break, mck_retype, mck_resume, mck_get, mck_valid):
|
||||||
array_id = self.data.array
|
array_id = self.data.array
|
||||||
volume = self.data.test_volume
|
volume = self.data.test_volume
|
||||||
device_id = self.data.device_id
|
device_id = self.data.device_id
|
||||||
@ -828,6 +831,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
target_slo, target_workload, target_extra_specs)
|
target_slo, target_workload, target_extra_specs)
|
||||||
self.assertTrue(success)
|
self.assertTrue(success)
|
||||||
|
|
||||||
|
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
||||||
@ -845,7 +849,8 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
'remote_array': tpd.PowerMaxData.remote_array},
|
'remote_array': tpd.PowerMaxData.remote_array},
|
||||||
tpd.PowerMaxData.rep_extra_specs, False))
|
tpd.PowerMaxData.rep_extra_specs, False))
|
||||||
def test_migrate_volume_success_no_rep_to_rep(
|
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_check,
|
||||||
|
mck_valid):
|
||||||
self.common.rep_config = {'mode': utils.REP_SYNC,
|
self.common.rep_config = {'mode': utils.REP_SYNC,
|
||||||
'array': self.data.array}
|
'array': self.data.array}
|
||||||
array_id = self.data.array
|
array_id = self.data.array
|
||||||
@ -885,6 +890,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
target_extra_specs)
|
target_extra_specs)
|
||||||
self.assertTrue(success)
|
self.assertTrue(success)
|
||||||
|
|
||||||
|
@mock.patch.object(common.PowerMaxCommon, '_validate_rdfg_status')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
common.PowerMaxCommon, 'get_volume_metadata', return_value='')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
@ -893,7 +899,7 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
common.PowerMaxCommon, '_retype_volume',
|
common.PowerMaxCommon, '_retype_volume',
|
||||||
return_value=(True, tpd.PowerMaxData.defaultstoragegroup_name))
|
return_value=(True, tpd.PowerMaxData.defaultstoragegroup_name))
|
||||||
def test_migrate_volume_success_rep_to_rep(self, mck_retype, mck_remote,
|
def test_migrate_volume_success_rep_to_rep(self, mck_retype, mck_remote,
|
||||||
mck_get):
|
mck_get, mck_valid):
|
||||||
self.common.rep_config = {'mode': utils.REP_SYNC,
|
self.common.rep_config = {'mode': utils.REP_SYNC,
|
||||||
'array': self.data.array}
|
'array': self.data.array}
|
||||||
array_id = self.data.array
|
array_id = self.data.array
|
||||||
@ -1304,3 +1310,244 @@ class PowerMaxReplicationTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(extra_specs[utils.REP_CONFIG], rep_extra_specs)
|
self.assertEqual(extra_specs[utils.REP_CONFIG], rep_extra_specs)
|
||||||
self.assertTrue(resume_rdf)
|
self.assertTrue(resume_rdf)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
provision.PowerMaxProvision, 'verify_slo_workload',
|
||||||
|
return_value=(True, True))
|
||||||
|
@mock.patch.object(utils.PowerMaxUtils, 'get_rdf_management_group_name',
|
||||||
|
return_value=tpd.PowerMaxData.rdf_managed_async_grp)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_management_group_volume_consistency',
|
||||||
|
return_value=True)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_rdf_states',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_rdf_group_storage_group_exclusivity',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_is_replication_enabled',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
|
||||||
|
return_value=tpd.PowerMaxData.sg_details[0])
|
||||||
|
def test_validate_rdfg_status_success(
|
||||||
|
self, mck_get, mck_is_rep, mck_is_excl, mck_states, mck_cons,
|
||||||
|
mck_mgrp_name, mck_slo):
|
||||||
|
array = self.data.array
|
||||||
|
extra_specs = deepcopy(self.data.rep_extra_specs6)
|
||||||
|
extra_specs[utils.REP_MODE] = utils.REP_ASYNC
|
||||||
|
extra_specs[utils.REP_CONFIG] = self.data.rep_config_async
|
||||||
|
management_sg_name = self.data.rdf_managed_async_grp
|
||||||
|
rdfg = self.data.rdf_group_no_2
|
||||||
|
mode = utils.REP_ASYNC
|
||||||
|
|
||||||
|
self.common._validate_rdfg_status(array, extra_specs)
|
||||||
|
|
||||||
|
self.assertEqual(2, mck_get.call_count)
|
||||||
|
self.assertEqual(2, mck_is_rep.call_count)
|
||||||
|
self.assertEqual(2, mck_is_excl.call_count)
|
||||||
|
self.assertEqual(2, mck_states.call_count)
|
||||||
|
self.assertEqual(1, mck_cons.call_count)
|
||||||
|
self.assertEqual(1, mck_mgrp_name.call_count)
|
||||||
|
mck_is_rep.assert_called_with(array, management_sg_name)
|
||||||
|
mck_is_excl.assert_called_with(array, management_sg_name)
|
||||||
|
mck_states.assert_called_with(array, management_sg_name, rdfg, mode)
|
||||||
|
mck_cons.assert_called_with(array, management_sg_name, rdfg)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
provision.PowerMaxProvision, 'verify_slo_workload',
|
||||||
|
return_value=(True, True))
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_rdf_states',
|
||||||
|
return_value=False)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_rdf_group_storage_group_exclusivity',
|
||||||
|
return_value=True)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_is_replication_enabled',
|
||||||
|
return_value=True)
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
|
||||||
|
return_value=tpd.PowerMaxData.sg_details[0])
|
||||||
|
def test_validate_rdfg_status_failure_default_sg(
|
||||||
|
self, mck_get, mck_is_rep, mck_is_excl, mck_states, mck_slo):
|
||||||
|
array = self.data.array
|
||||||
|
extra_specs = deepcopy(self.data.rep_extra_specs6)
|
||||||
|
extra_specs[utils.REP_MODE] = utils.REP_ASYNC
|
||||||
|
extra_specs[utils.REP_CONFIG] = self.data.rep_config_async
|
||||||
|
rdfg = self.data.rdf_group_no_2
|
||||||
|
mode = utils.REP_ASYNC
|
||||||
|
disable_compression = self.utils.is_compression_disabled(extra_specs)
|
||||||
|
storage_group = self.utils.get_default_storage_group_name(
|
||||||
|
extra_specs['srp'], extra_specs['slo'], extra_specs['workload'],
|
||||||
|
disable_compression, True, extra_specs['rep_mode'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common._validate_rdfg_status,
|
||||||
|
array, extra_specs)
|
||||||
|
|
||||||
|
self.assertEqual(1, mck_get.call_count)
|
||||||
|
self.assertEqual(1, mck_is_rep.call_count)
|
||||||
|
self.assertEqual(1, mck_is_excl.call_count)
|
||||||
|
self.assertEqual(1, mck_states.call_count)
|
||||||
|
mck_is_rep.assert_called_with(array, storage_group)
|
||||||
|
mck_is_excl.assert_called_with(array, storage_group)
|
||||||
|
mck_states.assert_called_with(array, storage_group, rdfg, mode)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
provision.PowerMaxProvision, 'verify_slo_workload',
|
||||||
|
return_value=(True, True))
|
||||||
|
@mock.patch.object(utils.PowerMaxUtils, 'get_rdf_management_group_name',
|
||||||
|
return_value=tpd.PowerMaxData.rdf_managed_async_grp)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_management_group_volume_consistency',
|
||||||
|
return_value=False)
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_rdf_states',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_rdf_group_storage_group_exclusivity',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
'_validate_storage_group_is_replication_enabled',
|
||||||
|
side_effect=[True, True])
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
|
||||||
|
return_value=tpd.PowerMaxData.sg_details[0])
|
||||||
|
def test_validate_rdfg_status_failure_management_sg(
|
||||||
|
self, mck_get, mck_is_rep, mck_is_excl, mck_states, mck_cons,
|
||||||
|
mck_mgrp_name, mck_slo):
|
||||||
|
array = self.data.array
|
||||||
|
extra_specs = deepcopy(self.data.rep_extra_specs6)
|
||||||
|
extra_specs[utils.REP_MODE] = utils.REP_ASYNC
|
||||||
|
extra_specs[utils.REP_CONFIG] = self.data.rep_config_async
|
||||||
|
management_sg_name = self.data.rdf_managed_async_grp
|
||||||
|
rdfg = self.data.rdf_group_no_2
|
||||||
|
mode = utils.REP_ASYNC
|
||||||
|
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.common._validate_rdfg_status,
|
||||||
|
array, extra_specs)
|
||||||
|
|
||||||
|
self.assertEqual(2, mck_get.call_count)
|
||||||
|
self.assertEqual(2, mck_is_rep.call_count)
|
||||||
|
self.assertEqual(2, mck_is_excl.call_count)
|
||||||
|
self.assertEqual(2, mck_states.call_count)
|
||||||
|
self.assertEqual(1, mck_cons.call_count)
|
||||||
|
self.assertEqual(1, mck_mgrp_name.call_count)
|
||||||
|
mck_is_rep.assert_called_with(array, management_sg_name)
|
||||||
|
mck_is_excl.assert_called_with(array, management_sg_name)
|
||||||
|
mck_states.assert_called_with(array, management_sg_name, rdfg, mode)
|
||||||
|
mck_cons.assert_called_with(array, management_sg_name, rdfg)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rep',
|
||||||
|
return_value={'rdf': True})
|
||||||
|
def test_validate_storage_group_is_replication_enabled_success(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
is_valid = self.common._validate_storage_group_is_replication_enabled(
|
||||||
|
array, storage_group)
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rep',
|
||||||
|
return_value={'rdf': False})
|
||||||
|
def test_validate_storage_group_is_replication_enabled_failure(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
is_valid = self.common._validate_storage_group_is_replication_enabled(
|
||||||
|
array, storage_group)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rdf_group_state',
|
||||||
|
return_value=[utils.RDF_SYNC_STATE])
|
||||||
|
def test_validate_storage_group_rdf_states_success(self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
rdf_group_no = self.data.rdf_group_no_1
|
||||||
|
rep_mode = utils.REP_SYNC
|
||||||
|
is_valid = self.common._validate_storage_group_rdf_states(
|
||||||
|
array, storage_group, rdf_group_no, rep_mode)
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group, rdf_group_no)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rdf_group_state',
|
||||||
|
return_value=[utils.RDF_SYNC_STATE, utils.RDF_ACTIVE])
|
||||||
|
def test_validate_storage_group_rdf_states_multi_async_state_failure(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
rdf_group_no = self.data.rdf_group_no_1
|
||||||
|
rep_mode = utils.REP_ASYNC
|
||||||
|
is_valid = self.common._validate_storage_group_rdf_states(
|
||||||
|
array, storage_group, rdf_group_no, rep_mode)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group, rdf_group_no)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rdf_group_state',
|
||||||
|
return_value=['invalid_state'])
|
||||||
|
def test_validate_storage_group_rdf_states_invalid_state_failure(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
rdf_group_no = self.data.rdf_group_no_1
|
||||||
|
rep_mode = utils.REP_ASYNC
|
||||||
|
is_valid = self.common._validate_storage_group_rdf_states(
|
||||||
|
array, storage_group, rdf_group_no, rep_mode)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group, rdf_group_no)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rdf_groups',
|
||||||
|
return_value=[tpd.PowerMaxData.rdf_group_no_1])
|
||||||
|
def test_validate_rdf_group_storage_group_exclusivity_success(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
is_valid = self.common._validate_rdf_group_storage_group_exclusivity(
|
||||||
|
array, storage_group)
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group_rdf_groups',
|
||||||
|
return_value=[tpd.PowerMaxData.rdf_group_no_1,
|
||||||
|
tpd.PowerMaxData.rdf_group_no_2])
|
||||||
|
def test_validate_rdf_group_storage_group_exclusivity_failure(
|
||||||
|
self, mck_get):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.storagegroup_name_f
|
||||||
|
is_valid = self.common._validate_rdf_group_storage_group_exclusivity(
|
||||||
|
array, storage_group)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
mck_get.assert_called_once_with(array, storage_group)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_volumes_in_storage_group',
|
||||||
|
return_value=[tpd.PowerMaxData.device_id])
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_rdf_group_volume_list',
|
||||||
|
return_value=[tpd.PowerMaxData.device_id])
|
||||||
|
def test_validate_management_group_volume_consistency_success(
|
||||||
|
self, mck_rdf, mck_sg):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.rdf_managed_async_grp
|
||||||
|
rdf_group = self.data.rdf_group_no_1
|
||||||
|
is_valid = self.common._validate_management_group_volume_consistency(
|
||||||
|
array, storage_group, rdf_group)
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
mck_rdf.assert_called_once_with(array, rdf_group)
|
||||||
|
mck_sg.assert_called_once_with(array, storage_group)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_volumes_in_storage_group',
|
||||||
|
return_value=[tpd.PowerMaxData.device_id])
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_rdf_group_volume_list',
|
||||||
|
return_value=[tpd.PowerMaxData.device_id,
|
||||||
|
tpd.PowerMaxData.device_id2])
|
||||||
|
def test_validate_management_group_volume_consistency_failure(
|
||||||
|
self, mck_rdf, mck_sg):
|
||||||
|
array = self.data.array
|
||||||
|
storage_group = self.data.rdf_managed_async_grp
|
||||||
|
rdf_group = self.data.rdf_group_no_1
|
||||||
|
is_valid = self.common._validate_management_group_volume_consistency(
|
||||||
|
array, storage_group, rdf_group)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
mck_rdf.assert_called_once_with(array, rdf_group)
|
||||||
|
mck_sg.assert_called_once_with(array, storage_group)
|
||||||
|
@ -1816,6 +1816,20 @@ class PowerMaxRestTest(test.TestCase):
|
|||||||
self.data.array, 'replication', ref_get_resource)
|
self.data.array, 'replication', ref_get_resource)
|
||||||
self.assertEqual(states, [utils.RDF_SUSPENDED_STATE])
|
self.assertEqual(states, [utils.RDF_SUSPENDED_STATE])
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_resource',
|
||||||
|
return_value={'rdfgs': [100, 200]})
|
||||||
|
def test_get_storage_group_rdf_groups(self, mck_get):
|
||||||
|
rdf_groups = self.rest.get_storage_group_rdf_groups(
|
||||||
|
self.data.array, self.data.storagegroup_name_f)
|
||||||
|
self.assertEqual([100, 200], rdf_groups)
|
||||||
|
|
||||||
|
@mock.patch.object(rest.PowerMaxRest, 'get_resource',
|
||||||
|
return_value={"name": ["00038", "00039"]})
|
||||||
|
def test_get_rdf_group_volume_list(self, mck_get):
|
||||||
|
volumes_list = self.rest.get_rdf_group_volume_list(
|
||||||
|
self.data.array, self.data.rdf_group_no_1)
|
||||||
|
self.assertEqual(["00038", "00039"], volumes_list)
|
||||||
|
|
||||||
@mock.patch.object(rest.PowerMaxRest, 'get_resource')
|
@mock.patch.object(rest.PowerMaxRest, 'get_resource')
|
||||||
def test_get_rdf_pair_volume(self, mck_get):
|
def test_get_rdf_pair_volume(self, mck_get):
|
||||||
rdf_grp_no = self.data.rdf_group_no_1
|
rdf_grp_no = self.data.rdf_group_no_1
|
||||||
|
@ -1958,6 +1958,9 @@ class PowerMaxCommon(object):
|
|||||||
return volume_name
|
return volume_name
|
||||||
|
|
||||||
array = extra_specs[utils.ARRAY]
|
array = extra_specs[utils.ARRAY]
|
||||||
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
|
self._validate_rdfg_status(array, extra_specs)
|
||||||
|
|
||||||
# Check if the volume being deleted is a
|
# Check if the volume being deleted is a
|
||||||
# source or target for copy session
|
# source or target for copy session
|
||||||
self._sync_check(array, device_id, extra_specs,
|
self._sync_check(array, device_id, extra_specs,
|
||||||
@ -2021,6 +2024,9 @@ class PowerMaxCommon(object):
|
|||||||
if self.utils.is_replication_enabled(extra_specs):
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
is_re, rep_mode = True, extra_specs['rep_mode']
|
is_re, rep_mode = True, extra_specs['rep_mode']
|
||||||
|
|
||||||
|
if is_re:
|
||||||
|
self._validate_rdfg_status(array, extra_specs)
|
||||||
|
|
||||||
storagegroup_name = self.masking.get_or_create_default_storage_group(
|
storagegroup_name = self.masking.get_or_create_default_storage_group(
|
||||||
array, extra_specs[utils.SRP], extra_specs[utils.SLO],
|
array, extra_specs[utils.SRP], extra_specs[utils.SLO],
|
||||||
extra_specs[utils.WORKLOAD], extra_specs,
|
extra_specs[utils.WORKLOAD], extra_specs,
|
||||||
@ -3697,6 +3703,11 @@ class PowerMaxCommon(object):
|
|||||||
utils.BACKEND_ID_LEGACY_REP)
|
utils.BACKEND_ID_LEGACY_REP)
|
||||||
backend_ids_differ = curr_backend_id != tgt_backend_id
|
backend_ids_differ = curr_backend_id != tgt_backend_id
|
||||||
|
|
||||||
|
if was_rep_enabled:
|
||||||
|
self._validate_rdfg_status(array, extra_specs)
|
||||||
|
if is_rep_enabled:
|
||||||
|
self._validate_rdfg_status(array, target_extra_specs)
|
||||||
|
|
||||||
# Scenario 1: Rep -> Non-Rep
|
# Scenario 1: Rep -> Non-Rep
|
||||||
# Scenario 2: Cleanup for Rep -> Diff Rep type
|
# Scenario 2: Cleanup for Rep -> Diff Rep type
|
||||||
if (was_rep_enabled and not is_rep_enabled) or backend_ids_differ:
|
if (was_rep_enabled and not is_rep_enabled) or backend_ids_differ:
|
||||||
@ -6074,3 +6085,170 @@ class PowerMaxCommon(object):
|
|||||||
array, volume, device_id, volume_name, extra_specs, False)
|
array, volume, device_id, volume_name, extra_specs, False)
|
||||||
self._delete_from_srp(
|
self._delete_from_srp(
|
||||||
array, device_id, volume_name, extra_specs)
|
array, device_id, volume_name, extra_specs)
|
||||||
|
|
||||||
|
def _validate_rdfg_status(self, array, extra_specs):
|
||||||
|
"""Validate RDF group states before and after various operations
|
||||||
|
|
||||||
|
:param array: array serial number -- str
|
||||||
|
:param extra_specs: volume extra specs -- dict
|
||||||
|
"""
|
||||||
|
rep_extra_specs = self._get_replication_extra_specs(
|
||||||
|
extra_specs, extra_specs[utils.REP_CONFIG])
|
||||||
|
rep_mode = extra_specs['rep_mode']
|
||||||
|
rdf_group_no = rep_extra_specs['rdf_group_no']
|
||||||
|
|
||||||
|
# Get default storage group for volume
|
||||||
|
disable_compression = self.utils.is_compression_disabled(extra_specs)
|
||||||
|
storage_group_name = self.utils.get_default_storage_group_name(
|
||||||
|
extra_specs['srp'], extra_specs['slo'], extra_specs['workload'],
|
||||||
|
disable_compression, True, extra_specs['rep_mode'])
|
||||||
|
|
||||||
|
# Check for storage group. Will be unavailable for first vol create
|
||||||
|
storage_group_details = self.rest.get_storage_group(
|
||||||
|
array, storage_group_name)
|
||||||
|
storage_group_available = storage_group_details is not None
|
||||||
|
|
||||||
|
if storage_group_available:
|
||||||
|
is_rep = self._validate_storage_group_is_replication_enabled(
|
||||||
|
array, storage_group_name)
|
||||||
|
is_exclusive = self._validate_rdf_group_storage_group_exclusivity(
|
||||||
|
array, storage_group_name)
|
||||||
|
is_valid_states = self._validate_storage_group_rdf_states(
|
||||||
|
array, storage_group_name, rdf_group_no, rep_mode)
|
||||||
|
if not (is_rep and is_exclusive and is_valid_states):
|
||||||
|
msg = (_('RDF validation for storage group %s failed. Please '
|
||||||
|
'see logged error messages for specific details.'
|
||||||
|
) % storage_group_name)
|
||||||
|
raise exception.VolumeBackendAPIException(msg)
|
||||||
|
|
||||||
|
# Perform checks against Async or Metro management storage groups
|
||||||
|
if rep_mode is not utils.REP_SYNC:
|
||||||
|
management_sg_name = self.utils.get_rdf_management_group_name(
|
||||||
|
extra_specs['rep_config'])
|
||||||
|
management_sg_details = self.rest.get_storage_group(
|
||||||
|
array, management_sg_name)
|
||||||
|
management_sg_available = management_sg_details is not None
|
||||||
|
|
||||||
|
if management_sg_available:
|
||||||
|
is_rep = self._validate_storage_group_is_replication_enabled(
|
||||||
|
array, management_sg_name)
|
||||||
|
is_excl = self._validate_rdf_group_storage_group_exclusivity(
|
||||||
|
array, management_sg_name)
|
||||||
|
is_valid_states = self._validate_storage_group_rdf_states(
|
||||||
|
array, management_sg_name, rdf_group_no, rep_mode)
|
||||||
|
is_cons = self._validate_management_group_volume_consistency(
|
||||||
|
array, management_sg_name, rdf_group_no)
|
||||||
|
if not (is_rep and is_excl and is_valid_states and is_cons):
|
||||||
|
msg = (_(
|
||||||
|
'RDF validation for storage group %s failed. Please '
|
||||||
|
'see logged error messages for specific details.')
|
||||||
|
% management_sg_name)
|
||||||
|
raise exception.VolumeBackendAPIException(msg)
|
||||||
|
|
||||||
|
def _validate_storage_group_is_replication_enabled(
|
||||||
|
self, array, storage_group_name):
|
||||||
|
"""Validate that a storage groups is marked as RDF enabled
|
||||||
|
|
||||||
|
:param array: array serial number -- str
|
||||||
|
:param storage_group_name: name of the storage group -- str
|
||||||
|
:returns: consistency validation checks passed -- boolean
|
||||||
|
"""
|
||||||
|
is_valid = True
|
||||||
|
sg_details = self.rest.get_storage_group_rep(array, storage_group_name)
|
||||||
|
sg_rdf_enabled = sg_details.get('rdf', False)
|
||||||
|
if not sg_rdf_enabled:
|
||||||
|
LOG.error('Storage group %s is expected to be RDF enabled but '
|
||||||
|
'is not. Please check that all volumes in this storage '
|
||||||
|
'group are RDF enabled and part of the same RDFG.',
|
||||||
|
storage_group_name)
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def _validate_storage_group_rdf_states(
|
||||||
|
self, array, storage_group_name, rdf_group_no, rep_mode):
|
||||||
|
"""Validate that the RDF states found for storage groups are valid.
|
||||||
|
|
||||||
|
:param array: array serial number -- str
|
||||||
|
:param storage_group_name: name of the storage group -- str
|
||||||
|
:param rep_mode: replication mode being used -- str
|
||||||
|
:returns: consistency validation checks passed -- boolean
|
||||||
|
"""
|
||||||
|
is_valid = True
|
||||||
|
sg_rdf_states = self.rest.get_storage_group_rdf_group_state(
|
||||||
|
array, storage_group_name, rdf_group_no)
|
||||||
|
# Verify Async & Metro modes only have a single state
|
||||||
|
if rep_mode is not utils.REP_SYNC:
|
||||||
|
if len(sg_rdf_states) > 1:
|
||||||
|
sg_states_str = (', '.join(sg_rdf_states))
|
||||||
|
LOG.error('More than one RDFG state found for storage group '
|
||||||
|
'%s. We expect a single state for all volumes when '
|
||||||
|
'using %s replication mode. Found %s states.',
|
||||||
|
storage_group_name, rep_mode, sg_states_str)
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
# Determine which list of valid states to use
|
||||||
|
if rep_mode is utils.REP_SYNC:
|
||||||
|
valid_states = utils.RDF_VALID_STATES_SYNC
|
||||||
|
elif rep_mode is utils.REP_ASYNC:
|
||||||
|
valid_states = utils.RDF_VALID_STATES_ASYNC
|
||||||
|
else:
|
||||||
|
valid_states = utils.RDF_VALID_STATES_METRO
|
||||||
|
|
||||||
|
# Validate storage group states
|
||||||
|
for state in sg_rdf_states:
|
||||||
|
if state.lower() not in valid_states:
|
||||||
|
valid_states_str = (', '.join(valid_states))
|
||||||
|
LOG.error('Invalid RDF state found for storage group %s. '
|
||||||
|
'Found state %s. Valid states are %s.',
|
||||||
|
storage_group_name, state, valid_states_str)
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def _validate_rdf_group_storage_group_exclusivity(
|
||||||
|
self, array, storage_group_name):
|
||||||
|
"""Validate that a storage group only has one RDF group.
|
||||||
|
|
||||||
|
:param array: array serial number -- str
|
||||||
|
:param storage_group_name: name of storage group -- str
|
||||||
|
:returns: consistency validation checks passed -- boolean
|
||||||
|
"""
|
||||||
|
is_valid = True
|
||||||
|
sg_rdf_groups = self.rest.get_storage_group_rdf_groups(
|
||||||
|
array, storage_group_name)
|
||||||
|
if len(sg_rdf_groups) > 1:
|
||||||
|
rdf_groups_str = ', '.join(sg_rdf_groups)
|
||||||
|
LOG.error('Detected more than one RDF group associated with '
|
||||||
|
'storage group %s. Only one RDFG should be associated '
|
||||||
|
'with a storage group. Found RDF groups %s',
|
||||||
|
storage_group_name, rdf_groups_str)
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def _validate_management_group_volume_consistency(
|
||||||
|
self, array, management_sg_name, rdf_group_number):
|
||||||
|
"""Validate volume consistency between management SG and RDF group
|
||||||
|
|
||||||
|
:param array: array serial number -- str
|
||||||
|
:param management_sg_name: name of storage group -- str
|
||||||
|
:param rdf_group_number: rdf group number to check -- str
|
||||||
|
:returns: consistency validation checks passed -- boolean
|
||||||
|
"""
|
||||||
|
is_valid = True
|
||||||
|
rdfg_volumes = self.rest.get_rdf_group_volume_list(
|
||||||
|
array, rdf_group_number)
|
||||||
|
sg_volumes = self.rest.get_volumes_in_storage_group(
|
||||||
|
array, management_sg_name)
|
||||||
|
missing_volumes = list()
|
||||||
|
for rdfg_volume in rdfg_volumes:
|
||||||
|
if rdfg_volume not in sg_volumes:
|
||||||
|
missing_volumes.append(rdfg_volume)
|
||||||
|
if missing_volumes:
|
||||||
|
missing_volumes_str = ', '.join(missing_volumes)
|
||||||
|
LOG.error(
|
||||||
|
'Inconsistency found between management group %s and RDF '
|
||||||
|
'group %s. The following volumes are not in the management '
|
||||||
|
'storage group %s. All Asynchronous and Metro volumes must '
|
||||||
|
'be managed together.',
|
||||||
|
management_sg_name, rdf_group_number, missing_volumes_str)
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
@ -2273,6 +2273,18 @@ class PowerMaxRest(object):
|
|||||||
|
|
||||||
return rdf_group.get('states', list()) if rdf_group else dict()
|
return rdf_group.get('states', list()) if rdf_group else dict()
|
||||||
|
|
||||||
|
def get_storage_group_rdf_groups(self, array, storage_group):
|
||||||
|
"""Get a list of rdf group numbers used by a storage group.
|
||||||
|
|
||||||
|
:param array: the array serial number -- str
|
||||||
|
:param storage_group: the storage group name to check -- str
|
||||||
|
:return: RDFGs associated with the storage group -- dict
|
||||||
|
"""
|
||||||
|
resource = ('storagegroup/%(storage_group)s/rdf_group' % {
|
||||||
|
'storage_group': storage_group})
|
||||||
|
storage_group_details = self.get_resource(array, REPLICATION, resource)
|
||||||
|
return storage_group_details['rdfgs']
|
||||||
|
|
||||||
def get_rdf_group_list(self, array):
|
def get_rdf_group_list(self, array):
|
||||||
"""Get rdf group list from array.
|
"""Get rdf group list from array.
|
||||||
|
|
||||||
@ -2280,6 +2292,18 @@ class PowerMaxRest(object):
|
|||||||
"""
|
"""
|
||||||
return self.get_resource(array, REPLICATION, 'rdf_group')
|
return self.get_resource(array, REPLICATION, 'rdf_group')
|
||||||
|
|
||||||
|
def get_rdf_group_volume_list(self, array, rdf_group_no):
|
||||||
|
"""Get a list of all volumes in an RDFG.
|
||||||
|
|
||||||
|
:param array: the array serial number -- str
|
||||||
|
:param rdf_group_no: the RDF group number -- str
|
||||||
|
:return: RDFG volume list -- list
|
||||||
|
"""
|
||||||
|
resource = ('rdf_group/%(rdf_group)s/volume' % {
|
||||||
|
'rdf_group': rdf_group_no})
|
||||||
|
rdf_group_volumes = self.get_resource(array, REPLICATION, resource)
|
||||||
|
return rdf_group_volumes['name']
|
||||||
|
|
||||||
def get_rdf_group_volume(self, array, src_device_id):
|
def get_rdf_group_volume(self, array, src_device_id):
|
||||||
"""Get the RDF details for a volume.
|
"""Get the RDF details for a volume.
|
||||||
|
|
||||||
|
@ -79,6 +79,11 @@ RDF_FAILEDOVER_STATE = 'failed over'
|
|||||||
RDF_ACTIVE = 'active'
|
RDF_ACTIVE = 'active'
|
||||||
RDF_ACTIVEACTIVE = 'activeactive'
|
RDF_ACTIVEACTIVE = 'activeactive'
|
||||||
RDF_ACTIVEBIAS = 'activebias'
|
RDF_ACTIVEBIAS = 'activebias'
|
||||||
|
RDF_VALID_STATES_SYNC = [RDF_SYNC_STATE, RDF_SYNCINPROG_STATE]
|
||||||
|
RDF_VALID_STATES_ASYNC = [RDF_CONSISTENT_STATE, RDF_SUSPENDED_STATE,
|
||||||
|
RDF_SYNCINPROG_STATE]
|
||||||
|
RDF_VALID_STATES_METRO = [RDF_ACTIVEBIAS, RDF_ACTIVEACTIVE,
|
||||||
|
RDF_SUSPENDED_STATE, RDF_SYNCINPROG_STATE]
|
||||||
RDF_CONS_EXEMPT = 'exempt'
|
RDF_CONS_EXEMPT = 'exempt'
|
||||||
RDF_ALLOW_METRO_DELETE = 'allow_delete_metro'
|
RDF_ALLOW_METRO_DELETE = 'allow_delete_metro'
|
||||||
RDF_GROUP_NO = 'rdf_group_number'
|
RDF_GROUP_NO = 'rdf_group_number'
|
||||||
|
Loading…
Reference in New Issue
Block a user