def test_policy_check_post_op(self, mock_load, mock_load_all, mock_event): cluster_id = CLUSTER_ID # Note: policy is mocked policy = mock.Mock() policy.id = 'FAKE_POLICY_ID' policy.TARGET = [('AFTER', 'OBJECT_ACTION')] policy.cooldown = 0 # Note: policy binding is created but not stored pb = self._create_cp_binding(cluster_id, policy.id) self.assertIsNone(pb.last_op) mock_load_all.return_value = [pb] mock_load.return_value = policy entity = mock.Mock() action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) action.entity = entity res = action.policy_check(CLUSTER_ID, 'AFTER') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_OK, action.data['status']) mock_load_all.assert_called_once_with(action.context, cluster_id, sort='priority', filters={'enabled': True}) mock_load.assert_called_once_with(action.context, policy.id) # last_op was updated for POST check self.assertIsNotNone(pb.last_op) # pre_op is called, but post_op was not called self.assertEqual(0, policy.pre_op.call_count) policy.post_op.assert_called_once_with(cluster_id, action)
def test_policy_check_cooldown_inprogress(self, mock_load, mock_load_all): cluster_id = CLUSTER_ID # Note: policy is mocked policy = mock.Mock() policy.id = 'FAKE_POLICY_ID' policy.TARGET = [('AFTER', 'OBJECT_ACTION')] # Note: policy binding is created but not stored pb = self._create_cp_binding(cluster_id, policy.id) self.patchobject(pb, 'cooldown_inprogress', return_value=True) self.assertIsNone(pb.last_op) mock_load_all.return_value = [pb] mock_load.return_value = policy action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) res = action.policy_check(CLUSTER_ID, 'AFTER') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_ERROR, action.data['status']) self.assertEqual( 'Policy FAKE_POLICY_ID cooldown is still in ' 'progress.', six.text_type(action.data['reason'])) mock_load_all.assert_called_once_with(action.context, cluster_id, sort='priority', filters={'enabled': True}) mock_load.assert_called_once_with(action.context, policy.id) # last_op was updated for POST check self.assertIsNotNone(pb.last_op) # neither pre_op nor post_op was not called, due to cooldown self.assertEqual(0, policy.pre_op.call_count) self.assertEqual(0, policy.post_op.call_count)
def test_policy_check_pre_op(self, mock_load, mock_load_all, mock_event): cluster_id = CLUSTER_ID # Note: policy is mocked spec = { 'type': 'TestPolicy', 'version': '1.0', 'properties': { 'KEY2': 5 }, } policy = fakes.TestPolicy('test-policy', spec) policy.id = 'FAKE_POLICY_ID' policy.TARGET = [('BEFORE', 'OBJECT_ACTION')] # Note: policy binding is created but not stored pb = self._create_cp_binding(cluster_id, policy.id) self.assertIsNone(pb.last_op) mock_load_all.return_value = [pb] mock_load.return_value = policy entity = mock.Mock() action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) action.entity = entity res = action.policy_check(cluster_id, 'BEFORE') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_OK, action.data['status']) mock_load_all.assert_called_once_with(action.context, cluster_id, sort='priority', filters={'enabled': True}) mock_load.assert_called_once_with(action.context, policy.id) # last_op was not updated self.assertIsNone(pb.last_op)
def setUp(self): super(TestClusterActionNotification, self).setUp() ctx = utils.dummy_context() cluster_params = { 'id': uuidutils.generate_uuid(), 'init_at': timeutils.utcnow(True), 'min_size': 1, 'max_size': 10, 'timeout': 4, 'status': 'ACTIVE', 'status_reason': 'Good', 'user': '******', 'project': 'project1', } self.cluster = cluster.Cluster('CC', 5, uuidutils.generate_uuid(), **cluster_params) action_params = { 'id': uuidutils.generate_uuid(), 'name': 'fake_name', 'start_time': 1.23, 'status': 'RUNNING', 'status_reason': 'Good', 'user': '******', 'project': 'project1', } self.action = action_base.Action(uuidutils.generate_uuid(), 'CLUSTER_CREATE', ctx, **action_params)
def test_from_db_record(self): values = copy.deepcopy(self.action_values) obj = action_base.Action('OBJID', 'OBJECT_ACTION', self.ctx, **values) obj.store(self.ctx) record = db_api.action_get(self.ctx, obj.id) action_obj = action_base.Action._from_db_record(record) self.assertIsInstance(action_obj, action_base.Action) self.assertEqual(obj.id, action_obj.id) self.assertEqual(obj.action, action_obj.action) self.assertEqual(obj.name, action_obj.name) self.assertEqual(obj.target, action_obj.target) self.assertEqual(obj.cause, action_obj.cause) self.assertEqual(obj.owner, action_obj.owner) self.assertEqual(obj.interval, action_obj.interval) self.assertEqual(obj.start_time, action_obj.start_time) self.assertEqual(obj.end_time, action_obj.end_time) self.assertEqual(obj.timeout, action_obj.timeout) self.assertEqual(obj.status, action_obj.status) self.assertEqual(obj.status_reason, action_obj.status_reason) self.assertEqual(obj.inputs, action_obj.inputs) self.assertEqual(obj.outputs, action_obj.outputs) self.assertEqual(obj.depends_on, action_obj.depends_on) self.assertEqual(obj.depended_by, action_obj.depended_by) self.assertEqual(obj.created_time, action_obj.created_time) self.assertEqual(obj.updated_time, action_obj.updated_time) self.assertEqual(obj.deleted_time, action_obj.deleted_time) self.assertEqual(obj.data, action_obj.data)
def test_action_init_with_values(self): values = copy.deepcopy(self.action_values) values['id'] = 'FAKE_ID' values['description'] = 'FAKE_DESC' values['created_at'] = 'FAKE_CREATED_TIME' values['updated_at'] = 'FAKE_UPDATED_TIME' obj = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) self.assertEqual('FAKE_ID', obj.id) self.assertEqual('FAKE_NAME', obj.name) self.assertEqual('FAKE_DESC', obj.description) self.assertEqual(OBJID, obj.target) self.assertEqual('FAKE_CAUSE', obj.cause) self.assertEqual(OWNER_ID, obj.owner) self.assertEqual(60, obj.interval) self.assertEqual(0, obj.start_time) self.assertEqual(0, obj.end_time) self.assertEqual(120, obj.timeout) self.assertEqual('FAKE_STATUS', obj.status) self.assertEqual('FAKE_STATUS_REASON', obj.status_reason) self.assertEqual({'param': 'value'}, obj.inputs) self.assertEqual({'key': 'output_value'}, obj.outputs) self.assertEqual('FAKE_CREATED_TIME', obj.created_at) self.assertEqual('FAKE_UPDATED_TIME', obj.updated_at) self.assertEqual({'data_key': 'data_value'}, obj.data)
def setUp(self): super(TestNodeActionNotification, self).setUp() ctx = utils.dummy_context() node_params = { 'id': uuidutils.generate_uuid(), 'cluster_id': '', 'index': -1, 'init_at': timeutils.utcnow(True), 'status': 'ACTIVE', 'status_reason': 'Good', 'user': '******', 'project': 'project1', } self.node = node.Node('NN', uuidutils.generate_uuid(), **node_params) action_params = { 'id': uuidutils.generate_uuid(), 'name': 'fake_name', 'start_time': 1.23, 'status': 'RUNNING', 'status_reason': 'Good', 'user': '******', 'project': 'project1', } self.action = action_base.Action(uuidutils.generate_uuid(), 'NODE_CREATE', ctx, **action_params)
def test_policy_check_abort_in_middle(self, mock_check, mock_load, mock_load_all): cluster_id = CLUSTER_ID # Note: both policies are mocked policy1 = mock.Mock(id=uuidutils.generate_uuid(), cooldown=0, TARGET=[('AFTER', 'OBJECT_ACTION')]) policy1.name = 'P1' policy2 = mock.Mock(id=uuidutils.generate_uuid(), cooldown=0, TARGET=[('AFTER', 'OBJECT_ACTION')]) policy2.name = 'P2' action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) # Note: policy binding is created but not stored pb1 = self._create_cp_binding(cluster_id, policy1.id) pb2 = self._create_cp_binding(cluster_id, policy2.id) mock_load_all.return_value = [pb1, pb2] # mock return value for two calls mock_load.side_effect = [policy1, policy2] mock_check.side_effect = [False, True] res = action.policy_check(cluster_id, 'AFTER') self.assertIsNone(res) # post_op from policy1 was called, but post_op from policy2 was not policy1.post_op.assert_called_once_with(cluster_id, action) self.assertEqual(0, policy2.post_op.call_count) mock_load_all.assert_called_once_with( action.context, cluster_id, sort='priority', filters={'enabled': True}) calls = [mock.call(action.context, policy1.id)] mock_load.assert_has_calls(calls)
def test_policy_check_missing_target(self, mock_load, mock_load_all): cluster_id = 'FAKE_CLUSTER_ID' # Note: policy is mocked policy = mock.Mock() policy.id = 'FAKE_POLICY_ID' policy.TARGET = [('BEFORE', 'OBJECT_ACTION')] # Note: policy binding is created but not stored pb = self._create_cp_binding(cluster_id, policy.id) self.assertIsNone(pb.last_op) mock_load_all.return_value = [pb] mock_load.return_value = policy action = action_base.Action(cluster_id, 'OBJECT_ACTION_1', self.ctx) res = action.policy_check(cluster_id, 'AFTER') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_OK, action.data['status']) mock_load_all.assert_called_once_with(action.context, cluster_id, sort_keys=['priority'], filters={'enabled': True}) mock_load.assert_called_once_with(action.context, policy.id) # last_op was updated anyway self.assertIsNotNone(pb.last_op) # neither pre_op nor post_op was called, because target not match self.assertEqual(0, policy.pre_op.call_count) self.assertEqual(0, policy.post_op.call_count)
def test_policy_check_target_invalid(self, mock_load): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx) res = action.policy_check('FAKE_CLUSTER', 'WHEN') self.assertIsNone(res) self.assertEqual(0, mock_load.call_count)
def test_action_signal_cancel(self, mock_error, mock_call): values = copy.deepcopy(self.action_values) action = action_base.Action('OBJID', 'OBJECT_ACTION', self.ctx, **values) action.store(self.ctx) expected = [action.INIT, action.WAITING, action.READY, action.RUNNING] for status in expected: action.status = status result = action.signal(action.SIG_CANCEL) self.assertIsNone(result) self.assertEqual(1, mock_call.call_count) mock_call.reset_mock() invalid = [ action.SUSPENDED, action.SUCCEEDED, action.CANCELLED, action.FAILED ] for status in invalid: action.status = status result = action.signal(action.SIG_CANCEL) self.assertIsNone(result) self.assertEqual(0, mock_call.call_count) mock_call.reset_mock() self.assertEqual(1, mock_error.call_count) mock_error.reset_mock()
def test_from_db_record(self): values = copy.deepcopy(self.action_values) obj = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) obj.store(self.ctx) record = ao.Action.get(self.ctx, obj.id) action_obj = ab.Action._from_object(record) self.assertIsInstance(action_obj, ab.Action) self.assertEqual(obj.id, action_obj.id) self.assertEqual(obj.action, action_obj.action) self.assertEqual(obj.name, action_obj.name) self.assertEqual(obj.target, action_obj.target) self.assertEqual(obj.cause, action_obj.cause) self.assertEqual(obj.owner, action_obj.owner) self.assertEqual(obj.interval, action_obj.interval) self.assertEqual(obj.start_time, action_obj.start_time) self.assertEqual(obj.end_time, action_obj.end_time) self.assertEqual(obj.timeout, action_obj.timeout) self.assertEqual(obj.status, action_obj.status) self.assertEqual(obj.status_reason, action_obj.status_reason) self.assertEqual(obj.inputs, action_obj.inputs) self.assertEqual(obj.outputs, action_obj.outputs) self.assertEqual(common_utils.isotime(obj.created_at), common_utils.isotime(action_obj.created_at)) self.assertEqual(obj.updated_at, action_obj.updated_at) self.assertEqual(obj.data, action_obj.data) self.assertEqual(obj.user, action_obj.user) self.assertEqual(obj.project, action_obj.project) self.assertEqual(obj.domain, action_obj.domain)
def test_policy_check_missing_target(self, mock_load, mock_load_all, mock_pre_op, mock_post_op): cluster_id = 'FAKE_CLUSTER_ID' # Note: policy is mocked spec = { 'type': 'TestPolicy', 'version': '1.0', 'properties': {'KEY2': 5}, } policy = fakes.TestPolicy('test-policy', spec) policy.id = 'FAKE_POLICY_ID' policy.TARGET = [('BEFORE', 'OBJECT_ACTION')] # Note: policy binding is created but not stored pb = self._create_cp_binding(cluster_id, policy.id) self.assertIsNone(pb.last_op) mock_load_all.return_value = [pb] mock_load.return_value = policy mock_pre_op.return_value = None mock_post_op.return_value = None action = ab.Action(cluster_id, 'OBJECT_ACTION_1', self.ctx) res = action.policy_check(cluster_id, 'AFTER') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_OK, action.data['status']) mock_load_all.assert_called_once_with( action.context, cluster_id, sort='priority', filters={'enabled': True}) mock_load.assert_called_once_with(action.context, policy.id) # last_op was updated anyway self.assertIsNotNone(pb.last_op) # neither pre_op nor post_op was called, because target not match self.assertEqual(0, mock_pre_op.call_count) self.assertEqual(0, mock_post_op.call_count)
def test_action_to_dict(self, mock_dep_by, mock_dep_on): mock_dep_on.return_value = ['ACTION_1'] mock_dep_by.return_value = ['ACTION_2'] action = ab.Action('OBJID', 'OBJECT_ACTION', self.ctx, **self.action_values) action.id = 'FAKE_ID' expected = { 'id': 'FAKE_ID', 'name': 'FAKE_NAME', 'action': 'OBJECT_ACTION', 'target': 'OBJID', 'cause': 'FAKE_CAUSE', 'owner': 'FAKE_OWNER', 'interval': 60, 'start_time': 0, 'end_time': 0, 'timeout': 120, 'status': 'FAKE_STATUS', 'status_reason': 'FAKE_STATUS_REASON', 'inputs': {'param': 'value'}, 'outputs': {'key': 'output_value'}, 'depends_on': ['ACTION_1'], 'depended_by': ['ACTION_2'], 'created_at': None, 'updated_at': None, 'data': {'data_key': 'data_value'}, } res = action.to_dict() self.assertEqual(expected, res) mock_dep_on.assert_called_once_with(action.context, 'FAKE_ID') mock_dep_by.assert_called_once_with(action.context, 'FAKE_ID')
def test_check_signal_timeout(self, mock_debug): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx) action.id = 'FAKE_ID' action.timeout = 10 self.patchobject(action, 'is_timeout', return_value=True) res = action._check_signal() self.assertEqual(action.RES_TIMEOUT, res)
def test_load_all(self): result = ab.Action.load_all(self.ctx) self.assertEqual([], [c for c in result]) values = copy.deepcopy(self.action_values) action1 = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) action1.store(self.ctx) action2 = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) action2.store(self.ctx) # NOTE: we don't test all other parameters because the db api tests # already covered that results = list(ab.Action.load_all(self.ctx)) actions = [a.id for a in results] self.assertEqual(2, len(actions)) self.assertIn(action1.id, actions) self.assertIn(action2.id, actions)
def test_action_signal_bad_command(self, mock_call): values = copy.deepcopy(self.action_values) action1 = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) action1.store(self.ctx) result = action1.signal('BOGUS') self.assertIsNone(result) self.assertEqual(0, mock_call.call_count)
def test_policy_check_no_bindings(self, mock_load): action = ab.Action('OBJID', 'OBJECT_ACTION', self.ctx) mock_load.return_value = [] res = action.policy_check('FAKE_CLUSTER', 'BEFORE') self.assertIsNone(res) self.assertEqual(policy_mod.CHECK_OK, action.data['status']) mock_load.assert_called_once_with(action.context, 'FAKE_CLUSTER', sort='priority', filters={'enabled': True})
def test__check_result_true(self): cluster_id = CLUSTER_ID action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) action.data['status'] = policy_mod.CHECK_OK action.data['reason'] = "Completed policy checking." res = action._check_result('FAKE_POLICY_NAME') self.assertTrue(res)
def test_set_status(self, mock_sleep, mock_start, mock_abandon, mark_ready, mark_cancel, mark_fail, mark_succeed, mock_event, mock_error, mock_info): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id='FAKE_ID') action.entity = mock.Mock() action.set_status(action.RES_OK, 'FAKE_REASON') self.assertEqual(action.SUCCEEDED, action.status) self.assertEqual('FAKE_REASON', action.status_reason) mark_succeed.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY) action.set_status(action.RES_ERROR, 'FAKE_ERROR') self.assertEqual(action.FAILED, action.status) self.assertEqual('FAKE_ERROR', action.status_reason) mark_fail.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY, 'FAKE_ERROR') mark_fail.reset_mock() action.set_status(action.RES_TIMEOUT, 'TIMEOUT_ERROR') self.assertEqual(action.FAILED, action.status) self.assertEqual('TIMEOUT_ERROR', action.status_reason) mark_fail.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY, 'TIMEOUT_ERROR') mark_fail.reset_mock() action.set_status(action.RES_CANCEL, 'CANCELLED') self.assertEqual(action.CANCELLED, action.status) self.assertEqual('CANCELLED', action.status_reason) mark_cancel.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY) mark_fail.reset_mock() action.set_status(action.RES_LIFECYCLE_COMPLETE, 'LIFECYCLE COMPLETE') self.assertEqual(action.SUCCEEDED, action.status) self.assertEqual('LIFECYCLE COMPLETE', action.status_reason) mark_ready.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY) mark_fail.reset_mock() action.set_status(action.RES_RETRY, 'BUSY') self.assertEqual(action.READY, action.status) self.assertEqual('BUSY', action.status_reason) mock_start.assert_called_once_with(action.id) mock_sleep.assert_called_once_with(10) mock_abandon.assert_called_once_with(action.context, 'FAKE_ID', {'data': { 'retries': 1 }}) mark_fail.reset_mock() action.data = {'retries': 3} action.set_status(action.RES_RETRY, 'BUSY') self.assertEqual(action.RES_ERROR, action.status) mark_fail.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY, 'BUSY')
def test_action_delete(self): result = ab.Action.delete(self.ctx, 'non-existent') self.assertIsNone(result) values = copy.deepcopy(self.action_values) action1 = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) action1.store(self.ctx) result = ab.Action.delete(self.ctx, action1.id) self.assertIsNone(result)
def test_from_db_record_with_empty_fields(self): values = copy.deepcopy(self.action_values) del values['inputs'] del values['outputs'] obj = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, **values) obj.store(self.ctx) record = ao.Action.get(self.ctx, obj.id) action_obj = ab.Action._from_object(record) self.assertEqual({}, action_obj.inputs) self.assertEqual({}, action_obj.outputs)
def test_signal_cancel_children(self, mock_dobj, mock_signal): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=ACTION_ID) child_status_mock = mock.Mock() children = [] for child_id in CHILD_IDS: child = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=child_id) child.status = child.READY child.set_status = child_status_mock children.append(child) mock_dobj.return_value = CHILD_IDS action.load = mock.Mock() action.load.side_effect = children action.status = action.RUNNING action.signal_cancel() mock_dobj.assert_called_once_with(action.context, action.id) child_status_mock.assert_not_called() self.assertEqual(3, mock_signal.call_count) self.assertEqual(2, action.load.call_count)
def test_force_cancel_children(self, mock_dobj): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=ACTION_ID) child_status_mock = mock.Mock() children = [] for child_id in CHILD_IDS: child = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=child_id) child.status = child.WAITING_LIFECYCLE_COMPLETION child.set_status = child_status_mock children.append(child) mock_dobj.return_value = CHILD_IDS action.set_status = mock.Mock() action.load = mock.Mock() action.load.side_effect = children action.status = action.RUNNING action.force_cancel() mock_dobj.assert_called_once_with(action.context, action.id) self.assertEqual(2, child_status_mock.call_count) self.assertEqual(2, action.load.call_count)
def test_force_cancel_immutable(self, mock_dobj): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=ACTION_ID) action.load = mock.Mock() action.set_status = mock.Mock() mock_dobj.return_value = None action.status = action.FAILED self.assertRaises(exception.ActionImmutable, action.force_cancel) action.load.assert_not_called() action.set_status.assert_not_called()
def test_check_signal_signals_caught(self, mock_query): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx) action.id = 'FAKE_ID' action.timeout = 100 self.patchobject(action, 'is_timeout', return_value=False) sig_cmd = mock.Mock() mock_query.return_value = sig_cmd res = action._check_signal() self.assertEqual(sig_cmd, res) mock_query.assert_called_once_with(action.context, 'FAKE_ID')
def test_get_status(self, mock_get): mock_get.return_value = 'FAKE_STATUS' action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx) action.id = 'FAKE_ID' res = action.get_status() self.assertEqual('FAKE_STATUS', res) self.assertEqual('FAKE_STATUS', action.status) mock_get.assert_called_once_with(action.context, 'FAKE_ID', mock.ANY)
def test_check_result_false(self): cluster_id = CLUSTER_ID action = ab.Action(cluster_id, 'OBJECT_ACTION', self.ctx) action.data['status'] = policy_mod.CHECK_ERROR reason = ("Policy '%s' cooldown is still in progress." % 'FAKE_POLICY_2') action.data['reason'] = reason res = action._check_result('FAKE_POLICY_NAME') reason = ("Failed policy '%(name)s': %(reason)s" ) % {'name': 'FAKE_POLICY_NAME', 'reason': reason} self.assertFalse(res)
def test_force_cancel(self, mock_dobj): action = ab.Action(OBJID, 'OBJECT_ACTION', self.ctx, id=ACTION_ID) action.load = mock.Mock() action.set_status = mock.Mock() mock_dobj.return_value = None action.status = action.RUNNING action.force_cancel() action.load.assert_not_called() action.set_status.assert_called_once_with( action.RES_CANCEL, 'Action execution force cancelled')
def test_action_proc_fail_acquire(self, mock_acquire, mock_load, mock_clock): action = action_base.Action('OBJID', 'OBJECT_ACTION', self.ctx) mock_clock.return_value = 'TIMESTAMP' mock_acquire.return_value = None mock_load.return_value = action res = action_base.ActionProc(self.ctx, 'ACTION', 'WORKER') self.assertFalse(res) mock_clock.assert_called_once_with() mock_acquire.assert_called_once_with(action.context, 'ACTION', 'WORKER', 'TIMESTAMP')