Repository: StackStorm/st2

Issue:
Custom webhook fails action depending on payload.
Hi there, I've faced a weird issue.

Say it we have a webhook action like this: https://gist.github.com/dennybaa/00d1936af2f441cfff15
If I issue the following commands:
1. `curl -X POST --data "{\"message\": \"6fd781b849bb\"}" http://172.17.0.8:9101/v1/webhooks/sample`
2. `curl -X POST --data "{\"hits.rate_15m\": 0.4765927732670934, \"hits.count\": 30, \"tags\": [\"metric\"], \"@timestamp\": \"2015-04-23T13:58:28.319Z\", \"hits.rate_5m\": 0.31123263029226506, \"@version\": \"1\", \"message\": \"6fd781b849bb\", \"hits.rate_1m\": 0.08765952620175455}"`

In both cases trigger is dispatched. But the action takes place only in the #1 case.



Discussion:
I can successfully reproduce this. `0.8.3`

Thanks for the report.

Are we sure it's action not getting executed and not action getting executed and failing (e.g. echo failing because of unescaped special characters)?

In any case, I can dig in later.

I was thinking about it, but st2 passes json and it should take care of escaping of `{{trigger.body}}`.
As of bash itself, echo is not failing http://gyazo.com/a369b2b3ea87e9618622f802fcc978bb.

Anyway in the rulesengine log there should be an AUDIT action info about failed or executed action. Executed audit info is the log, but no failed info.

I am looking into this. 

https://gist.github.com/jfryman/e932eeb74f9b740dacf1

Weird finding! If there is a `.` character in the field name, the rule doesn't work. 

```
~/st2 git:master ❯❯❯ curl -X POST --data "{\"message\": \"6fd781b849bb\"}" http://localhost:9101/v1/webhooks/sample                                                                                                                                  ✭ ✖ ✱ ◼
{"message": "6fd781b849bb"}%                                                                                                                                                                                                                                 ~/st2 git:master ❯❯❯ curl -X POST --data "{\"message\": \"6fd781b849bb\", \"pi\": 3.14}" http://localhost:9101/v1/webhooks/sample                                                                                                                    ✭ ✖ ✱ ◼
{"message": "6fd781b849bb", "pi": 3.14}%                                                                                                                                                                                                                     ~/st2 git:master ❯❯❯ curl -X POST --data "{\"message\": \"6fd781b849bb\", \"pi.dot\": 3.14}" http://localhost:9101/v1/webhooks/sample                                                                                                                ✭ ✖ ✱ ◼
{"pi.dot": 3.14, "message": "6fd781b849bb"}%                                                                                                                                                                                                                 ~/st2 git:master ❯❯❯
```

```
~/st2 git:master ❯❯❯ cat /tmp/st2.webhook_sample.out                                                                                                                                                                                                 ✭ ✖ ✱ ◼
{u'message': u'6fd781b849bb'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bb'}
{u'message': u'6fd781b849bb', u'pi': 3.14}
~/st2 git:master ❯❯❯
```

Yeah, confirmed. Changing `.` to `_` makes it work.

```
~/st2 git:master ❯❯❯ cat /tmp/st2.webhook_sample.out                                                                                                                                                                                               ⏎ ✭ ✖ ✱ ◼
{u'message': u'6fd781b849bb'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bbc'}
{u'message': u'6fd781b849bb'}
{u'message': u'6fd781b849bb', u'pi': 3.14}
{u'hits_rate_15m': 0.4765927732670934, u'tags': [u'metric'], u'hits_rate_5m': 0.31123263029226506, u'@timestamp': u'2015-04-23T13:58:28.319Z', u'hits_count': 30, u'hits_rate_1m': 0.08765952620175455, u'message': u'6fd781b849bb', u'@version': u'1'}
~/st2 git:master ❯❯❯
```

We have been `pickled`.

Long story short, we use pickle for serializing objects for rabbitmq transport. We always knew JSON was the route to be taken but used pickle as a shortcut. In this particular case, the shortcut turned out to be disastrous. We need to switch to JSON serialization and it's an involved/major change. It has ability to break functionality. We should target this for 0.10.0 release. For now, the work around is not use `_` instead of `.` in payload fields. 

I've encountered a similar issue today when I was working on something today.

The issue is related to the payload containing dots and has nothing to do with pickle. If payload contains a dot, RulesEngine will blow up since TriggerInstance payload value can't contain dots because we don't use a special escaped dict field.

I will open a PR with fix for this shortly.

(In addition to that, some other special values in the payload also break the rules engine, but I will open a separate issue for that)


Resolving patch:
diff --git a/st2actions/st2actions/resultstracker.py b/st2actions/st2actions/resultstracker.py
--- a/st2actions/st2actions/resultstracker.py
+++ b/st2actions/st2actions/resultstracker.py
@@ -49,7 +49,7 @@ def start(self, wait=False):
 
     def wait(self):
         super(ResultsTracker, self).wait()
-        for thread in self._query_threads():
+        for thread in self._query_threads:
             thread.wait()
 
     def shutdown(self):
diff --git a/st2common/st2common/models/db/reactor.py b/st2common/st2common/models/db/reactor.py
--- a/st2common/st2common/models/db/reactor.py
+++ b/st2common/st2common/models/db/reactor.py
@@ -91,7 +91,7 @@ class TriggerInstanceDB(stormbase.StormFoundationDB):
         occurrence_time (datetime): time of occurrence of the trigger.
     """
     trigger = me.StringField()
-    payload = me.DictField()
+    payload = stormbase.EscapedDictField()
     occurrence_time = me.DateTimeField()
 
 


Test patch:
diff --git a/st2reactor/tests/unit/test_rule_matcher.py b/st2reactor/tests/unit/test_rule_matcher.py
--- a/st2reactor/tests/unit/test_rule_matcher.py
+++ b/st2reactor/tests/unit/test_rule_matcher.py
@@ -25,6 +25,7 @@
 
 
 class RuleMatcherTest(DbTestCase):
+    rules = []
 
     def test_get_matching_rules(self):
         self._setup_sample_trigger('st2.test.trigger1')
@@ -40,6 +41,22 @@ def test_get_matching_rules(self):
         self.assertTrue(matching_rules is not None)
         self.assertEqual(len(matching_rules), 1)
 
+    def test_trigger_instance_payload_with_special_values(self):
+        # Test a rule where TriggerInstance payload contains a dot (".") and $
+        self._setup_sample_trigger('st2.test.trigger2')
+        trigger_instance = container_utils.create_trigger_instance(
+            'dummy_pack_1.st2.test.trigger2',
+            {'k1': 't1_p_v', 'k2.k2': 'v2', 'k3.more.nested.deep': 'some.value',
+             'k4.even.more.nested$': 'foo', 'yep$aaa': 'b'},
+            datetime.datetime.utcnow()
+        )
+        trigger = get_trigger_db_by_ref(trigger_instance.trigger)
+        rules = self._get_sample_rules()
+        rules_matcher = RulesMatcher(trigger_instance, trigger, rules)
+        matching_rules = rules_matcher.get_matching_rules()
+        self.assertTrue(matching_rules is not None)
+        self.assertEqual(len(matching_rules), 1)
+
     def _setup_sample_trigger(self, name):
         trigtype = TriggerTypeDB()
         trigtype.name = name
@@ -58,7 +75,9 @@ def _setup_sample_trigger(self, name):
         Trigger.add_or_update(created)
 
     def _get_sample_rules(self):
-        rules = []
+        if self.rules:
+            # Make sure rules are created only once
+            return self.rules
 
         RULE_1 = {
             'enabled': True,
@@ -85,7 +104,7 @@ def _get_sample_rules(self):
         rule_api = RuleAPI(**RULE_1)
         rule_db = RuleAPI.to_model(rule_api)
         rule_db = Rule.add_or_update(rule_db)
-        rules.append(rule_db)
+        self.rules.append(rule_db)
 
         RULE_2 = {                      # Rule should match.
             'enabled': True,
@@ -112,7 +131,7 @@ def _get_sample_rules(self):
         rule_api = RuleAPI(**RULE_2)
         rule_db = RuleAPI.to_model(rule_api)
         rule_db = Rule.add_or_update(rule_db)
-        rules.append(rule_db)
+        self.rules.append(rule_db)
 
         RULE_3 = {
             'enabled': False,         # Disabled rule shouldn't match.
@@ -139,6 +158,6 @@ def _get_sample_rules(self):
         rule_api = RuleAPI(**RULE_3)
         rule_db = RuleAPI.to_model(rule_api)
         rule_db = Rule.add_or_update(rule_db)
-        rules.append(rule_db)
+        self.rules.append(rule_db)
 
-        return rules
+        return self.rules