diff --git a/mistral/engine/action_handler.py b/mistral/engine/action_handler.py index c673ca56..6e3fefeb 100644 --- a/mistral/engine/action_handler.py +++ b/mistral/engine/action_handler.py @@ -93,7 +93,11 @@ def get_action_input(action_name, input_dict, wf_name=None, wf_spec=None): if action_def.action_class: _inject_action_ctx_for_validating(action_def, input_dict) - e_utils.validate_input(action_def, input_dict) + + # NOTE(xylan): Don't validate action input if action initialization method + # contains ** argument. + if '**' not in action_def.input: + e_utils.validate_input(action_def, input_dict) if action_def.spec: # Ad-hoc action. diff --git a/mistral/tests/unit/actions/test_action_manager.py b/mistral/tests/unit/actions/test_action_manager.py index a92b1523..520f7302 100644 --- a/mistral/tests/unit/actions/test_action_manager.py +++ b/mistral/tests/unit/actions/test_action_manager.py @@ -38,6 +38,12 @@ class ActionManagerTest(base.DbTestCase): self._assert_single_item(action_list, name="nova.servers_get") self._assert_single_item(action_list, name="nova.volumes_delete") + server_find_action = self._assert_single_item( + action_list, + name="nova.servers_find" + ) + self.assertIn('**', server_find_action.input) + self._assert_single_item(action_list, name="keystone.users_list") self._assert_single_item(action_list, name="keystone.trusts_create") diff --git a/mistral/tests/unit/engine/test_run_action.py b/mistral/tests/unit/engine/test_run_action.py index 1b3e0e09..b40f1190 100644 --- a/mistral/tests/unit/engine/test_run_action.py +++ b/mistral/tests/unit/engine/test_run_action.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import mock from oslo_config import cfg from mistral.db.v2 import api as db_api +from mistral.db.v2.sqlalchemy import models from mistral import exceptions as exc from mistral.services import actions from mistral.tests.unit.engine import base @@ -103,3 +105,40 @@ class RunActionEngineTest(base.EngineTestCase): ) self.assertIn('concat', exception.message) + + @mock.patch('mistral.engine.action_handler.resolve_action_definition') + @mock.patch('mistral.engine.utils.validate_input') + @mock.patch('mistral.services.action_manager.get_action_class') + @mock.patch('mistral.engine.action_handler.run_action') + def test_run_action_with_kwargs_input(self, run_mock, class_mock, + validate_mock, def_mock): + action_def = models.ActionDefinition() + action_def.update({ + 'name': 'fake_action', + 'action_class': '', + 'attributes': {}, + 'description': '', + 'input': '**kwargs', + 'is_system': True, + 'scope': 'public' + }) + def_mock.return_value = action_def + + class_ret = mock.MagicMock() + class_mock.return_value = class_ret + + self.engine.start_action('fake_action', {'input': 'Hello'}) + + self.assertEqual(2, def_mock.call_count) + def_mock.assert_called_with('fake_action', None, None) + + self.assertEqual(0, validate_mock.call_count) + + class_ret.assert_called_once_with(input='Hello') + + run_mock.assert_called_once_with( + action_def, + {'input': 'Hello'}, + target=None, + async=False + ) diff --git a/mistral/utils/inspect_utils.py b/mistral/utils/inspect_utils.py index 2cbc099d..3e31ae69 100644 --- a/mistral/utils/inspect_utils.py +++ b/mistral/utils/inspect_utils.py @@ -71,4 +71,7 @@ def get_arg_list_as_str(func): else: arg_str_list.append("%s" % args[index]) + if argspec.keywords: + arg_str_list.append("**%s" % argspec.keywords) + return ", ".join(arg_str_list)