Repository: StackStorm/st2

Issue:
Infinite timeout in action parameters
The problem is that parameter `timeout: 0` in action is considered as 60 seconds and there is no option to make it "infinite", eg. no timeout.

Here is example action `actions/test_timeout.yaml`:

``` yaml

---
name: test_timeout
runner_type: local-shell-cmd
entry_point: ""
enabled: true
parameters:
  timeout:
    # works as default 60 seconds
    default: 0
  cmd:
    description: "Command to run"
    immutable: true
    default: "sleep 90 && echo 'done!'"
```

Running it: 

``` sh
# st2 run st2-pack-bug.test_timeout
..............................
id: 5586ca9b0a84b40a4a08a168
status: failed
result:
{
    "succeeded": false,
    "stdout": "",
    "return_code": -9,
    "failed": true,
    "stderr": "",
    "error": "Action failed to complete in 60 seconds"
}
```

Not sure if it's bug or not, but proposal is to consider `timeout: 0` as "no timeout", instead of default 60 seconds. Lots of cases when action doesn't needs any timeouts.

Because things like this in action:

``` yaml
parameters:
  timeout:
    default: 99999999
```

definitely looks like there should be a better way.



Discussion:
This seems like a bug indeed (we probably don't use strict truthfulness check in the code).

In any case, I would be interested to learn more about your use case for no timeout. Actions aren't really designed to run for really long periods of time (you should use sensor / workflow for that). In addition to that, a very long running action with no timeout can a bad idea since action could be blocked and consuming resources and we would never kill it which would cause quality of service issues.

On a related note - instead of allowing no timeout we should probably create a reasonable lower and upper boundary for a timeout :)

Long running actions - configuration management tools. Sometimes it's hard to predict for how long they can run and setting like `timeout: 99999` is a dirty way. The reality is - I want to run that process until it's finished and exits.

They don't have timeouts by default and I think from StackStorm side the right thing is to add an option for long as it possible execution, without limiting anything.


Resolving patch:
diff --git a/st2actions/st2actions/utils/param_utils.py b/st2actions/st2actions/utils/param_utils.py
--- a/st2actions/st2actions/utils/param_utils.py
+++ b/st2actions/st2actions/utils/param_utils.py
@@ -65,7 +65,7 @@ def _get_resolved_runner_params(runner_parameters, action_parameters,
         # pickup the override.
         if param_name in action_parameters:
             action_param = action_parameters[param_name]
-            if action_param.get('default', False):
+            if 'default' in action_param:
                 resolved_params[param_name] = action_param['default']
 
             # No further override (from liveaction) if param is immutable


Test patch:
diff --git a/st2actions/tests/unit/test_param_utils.py b/st2actions/tests/unit/test_param_utils.py
--- a/st2actions/tests/unit/test_param_utils.py
+++ b/st2actions/tests/unit/test_param_utils.py
@@ -51,7 +51,8 @@ def test_get_resolved_params(self):
             'some_key_that_aint_exist_in_action_or_runner': 'bar',
             'runnerint': 555,
             'runnerimmutable': 'failed_override',
-            'actionimmutable': 'failed_override'
+            'actionimmutable': 'failed_override',
+            'defaults_ovverriden_by_execution': 0,
         }
         liveaction_db = self._get_liveaction_model(params)
 
@@ -65,11 +66,16 @@ def test_get_resolved_params(self):
         self.assertEqual(runner_params.get('runnerstr'), 'defaultfoo')
         # Assert that a runner param from action exec is picked up.
         self.assertEqual(runner_params.get('runnerint'), 555)
-        # Assert that a runner param can be overriden by action param default.
+        # Assert that a runner param can be overridden by action param default.
         self.assertEqual(runner_params.get('runnerdummy'), 'actiondummy')
-        # Asser that runner param made immutable in action can use default value in runner.
+        # Assert that a runner param default can be overridden by 'falsey' action param default,
+        # (timeout: 0 case).
+        self.assertEqual(runner_params.get('runnerdefaultint'), 0)
+        # Assert that execution param overrides both runner and action params defaults.
+        self.assertEqual(runner_params.get('defaults_ovverriden_by_execution'), 0)
+        # Assert that runner param made immutable in action can use default value in runner.
         self.assertEqual(runner_params.get('runnerfoo'), 'FOO')
-        # Assert that an immutable param cannot be overriden by action param or execution param.
+        # Assert that an immutable param cannot be overridden by action param or execution param.
         self.assertEqual(runner_params.get('runnerimmutable'), 'runnerimmutable')
 
         # Asserts for action params.
@@ -77,7 +83,7 @@ def test_get_resolved_params(self):
         # Assert that a param that is provided in action exec that isn't in action or runner params
         # isn't in resolved params.
         self.assertEqual(action_params.get('some_key_that_aint_exist_in_action_or_runner'), None)
-        # Assert that an immutable param cannot be overriden by execution param.
+        # Assert that an immutable param cannot be overridden by execution param.
         self.assertEqual(action_params.get('actionimmutable'), 'actionimmutable')
         # Assert that none of runner params are present in action_params.
         for k in action_params:
@@ -104,9 +110,9 @@ def test_get_finalized_params(self):
         self.assertEqual(runner_params.get('runnerstr'), 'defaultfoo')
         # Assert that a runner param from action exec is picked up.
         self.assertEqual(runner_params.get('runnerint'), 555)
-        # Assert that a runner param can be overriden by action param default.
+        # Assert that a runner param can be overridden by action param default.
         self.assertEqual(runner_params.get('runnerdummy'), 'actiondummy')
-        # Assert that an immutable param cannot be overriden by action param or execution param.
+        # Assert that an immutable param cannot be overridden by action param or execution param.
         self.assertEqual(runner_params.get('runnerimmutable'), 'runnerimmutable')
 
         # Asserts for action params.
@@ -114,7 +120,7 @@ def test_get_finalized_params(self):
         # Assert that a param that is provided in action exec that isn't in action or runner params
         # isn't in resolved params.
         self.assertEqual(action_params.get('some_key_that_aint_exist_in_action_or_runner'), None)
-        # Assert that an immutable param cannot be overriden by execution param.
+        # Assert that an immutable param cannot be overridden by execution param.
         self.assertEqual(action_params.get('actionimmutable'), 'actionimmutable')
         # Assert that an action context param is set correctly.
         self.assertEqual(action_params.get('action_api_user'), 'noob')
@@ -141,7 +147,7 @@ def test_get_finalized_params_system_values(self):
         self.assertEqual(runner_params.get('runnerstr'), 'defaultfoo')
         # Assert that a runner param from action exec is picked up.
         self.assertEqual(runner_params.get('runnerint'), 555)
-        # Assert that an immutable param cannot be overriden by action param or execution param.
+        # Assert that an immutable param cannot be overridden by action param or execution param.
         self.assertEqual(runner_params.get('runnerimmutable'), 'runnerimmutable')
 
         # Asserts for action params.
@@ -167,7 +173,7 @@ def test_get_resolved_params_action_immutable(self):
         self.assertEqual(runner_params.get('runnerstr'), 'defaultfoo')
         # Assert that a runner param from action exec is picked up.
         self.assertEqual(runner_params.get('runnerint'), 555)
-        # Assert that a runner param can be overriden by action param default.
+        # Assert that a runner param can be overridden by action param default.
         self.assertEqual(runner_params.get('runnerdummy'), 'actiondummy')
 
         # Asserts for action params.