Repository: StackStorm/st2

Issue:
[st2reactor] Issue about validation fail  before dispatching trigger,Trigger type with reference "linux.cd875f82-58d6-47ff-9eff-11bd81493bf7" doesn't exist in the database
wen I  test sensor linux.FileWatchSensor: 

```shell
root@6206c3910cd6:/opt/stackstorm/packs/linux/sensors# st2 sensor get linux.FileWatchSensor 
+---------------+--------------------------------------------------------------+
| Property      | Value                                                        |
+---------------+--------------------------------------------------------------+
| id            | 5c2d8324d28ef50172db13e9                                     |
| uid           | sensor_type:linux:FileWatchSensor                            |
| ref           | linux.FileWatchSensor                                        |
| pack          | linux                                                        |
| name          | FileWatchSensor                                              |
| enabled       | True                                                         |
| entry_point   | sensors.file_watch_sensor.FileWatchSensor                    |
| artifact_uri  | file:///opt/stackstorm/packs/linux/sensors/file_watch_sensor |
|               | .py                                                          |
| trigger_types | [                                                            |
|               |     "linux.file_watch.line"                                  |
|               | ]                                                            |
| description   | Sensor which monitors files for new lines                    |
| metadata_file | sensors/file_watch_sensor.yaml                               |
+---------------+--------------------------------------------------------------+
```

I use rule as follow:

```shell
root@6206c3910cd6:/opt/stackstorm/packs/examples/rules# cat sample_rule_file_watch.yaml 
---
name: sample_rule_file_watch
pack: "examples"
description: Sample rule custom trigger type - add a file to be watched by file_watch_sensor in linux pack.
enabled: true

trigger:
  parameters:
    file_path: /tmp/st2_test
  type: linux.file_watch.line

criteria: {}

action:
  parameters:
    cmd: echo "{{trigger}}"
  ref: core.local
```

I execute cmd, like this:
   `echo >> liucong /tmp/st2_test`

and err log in `/var/log/st2/st2sensorcontainer.log` as follows:

```
2019-01-03 03:39:07,046 139683133441840 INFO tail [-] Tailing /tmp/st2_test
2019-01-03 03:39:07,047 139683133441840 INFO file_watch_sensor [-] Added file "/tmp/st2_test"
2019-01-03 03:41:00,852 139683181345520 WARNING trigger_dispatcher [-] Failed to validate payload ({'file_name': u'st2_test', 'line': u'liucong', 'file_path': u'/tmp/st2_test'}) for trigger "linux.cd875f82-58d6-47ff-9eff-11bd81493bf7": Trigger type with reference "linux.cd875f82-58d6-47ff-9eff-11bd81493bf7" doesn't exist in the database
```



Discussion:

Resolving patch:
diff --git a/contrib/linux/sensors/file_watch_sensor.py b/contrib/linux/sensors/file_watch_sensor.py
--- a/contrib/linux/sensors/file_watch_sensor.py
+++ b/contrib/linux/sensors/file_watch_sensor.py
@@ -1,5 +1,7 @@
 import os
 
+import eventlet
+
 from logshipper.tail import Tail
 
 from st2reactor.sensor.base import Sensor
@@ -42,6 +44,9 @@ def add_trigger(self, trigger):
         if not self._trigger:
             raise Exception('Trigger %s did not contain a ref.' % trigger)
 
+        # Wait a bit to avoid initialization race in logshipper library
+        eventlet.sleep(1.0)
+
         self._tail.add_file(filename=file_path)
         self._logger.info('Added file "%s"' % (file_path))
 
diff --git a/st2common/st2common/services/trigger_dispatcher.py b/st2common/st2common/services/trigger_dispatcher.py
--- a/st2common/st2common/services/trigger_dispatcher.py
+++ b/st2common/st2common/services/trigger_dispatcher.py
@@ -40,7 +40,7 @@ def dispatch(self, trigger, payload=None, trace_tag=None, throw_on_validation_er
         """
         Method which dispatches the trigger.
 
-        :param trigger: Reference to the TriggerType (<pack>.<name>).
+        :param trigger: Reference to the TriggerTypeDB (<pack>.<name>) or TriggerDB object.
         :type trigger: ``str``
 
         :param payload: Trigger payload.
@@ -64,7 +64,7 @@ def dispatch_with_context(self, trigger, payload=None, trace_context=None,
         """
         Method which dispatches the trigger.
 
-        :param trigger: Reference to the TriggerType (<pack>.<name>).
+        :param trigger: Reference to the TriggerTypeDB (<pack>.<name>) or TriggerDB object.
         :type trigger: ``str``
 
         :param payload: Trigger payload.
diff --git a/st2common/st2common/validators/api/reactor.py b/st2common/st2common/validators/api/reactor.py
--- a/st2common/st2common/validators/api/reactor.py
+++ b/st2common/st2common/validators/api/reactor.py
@@ -14,8 +14,9 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import six
 
+import six
+import uuid
 from oslo_config import cfg
 from apscheduler.triggers.cron import CronTrigger
 
@@ -113,7 +114,7 @@ def validate_trigger_payload(trigger_type_ref, payload, throw_on_inexistent_trig
     """
     This function validates trigger payload parameters for system and user-defined triggers.
 
-    :param trigger_type_ref: Reference of a trigger type or a trigger dictionary object.
+    :param trigger_type_ref: Reference of a trigger type / trigger / trigger dictionary object.
     :type trigger_type_ref: ``str``
 
     :param payload: Trigger payload.
@@ -144,7 +145,23 @@ def validate_trigger_payload(trigger_type_ref, payload, throw_on_inexistent_trig
         # System trigger
         payload_schema = SYSTEM_TRIGGER_TYPES[trigger_type_ref]['payload_schema']
     else:
+        # We assume Trigger ref and not TriggerType ref is passed in if second
+        # part (trigger name) is a valid UUID version 4
+        try:
+            trigger_uuid = uuid.UUID(trigger_type_ref.split('.')[-1])
+        except ValueError:
+            is_trigger_db = False
+        else:
+            is_trigger_db = (trigger_uuid.version == 4)
+
+        if is_trigger_db:
+            trigger_db = triggers.get_trigger_db_by_ref(trigger_type_ref)
+
+            if trigger_db:
+                trigger_type_ref = trigger_db.type
+
         trigger_type_db = triggers.get_trigger_type_db(trigger_type_ref)
+
         if not trigger_type_db:
             # Trigger doesn't exist in the database
             if throw_on_inexistent_trigger:


Test patch:
diff --git a/st2reactor/tests/unit/test_sensor_service.py b/st2reactor/tests/unit/test_sensor_service.py
--- a/st2reactor/tests/unit/test_sensor_service.py
+++ b/st2reactor/tests/unit/test_sensor_service.py
@@ -28,9 +28,14 @@
 }
 
 
-class TriggerTypeMock(object):
-    def __init__(self, schema={}):
-        self.payload_schema = schema
+class TriggerTypeDBMock(object):
+    def __init__(self, schema=None):
+        self.payload_schema = schema or {}
+
+
+class TriggerDBMock(object):
+    def __init__(self, type=None):
+        self.type = type
 
 
 class SensorServiceTestCase(unittest2.TestCase):
@@ -54,7 +59,7 @@ def tearDown(self):
         cfg.CONF.system.validate_trigger_payload = self.validate_trigger_payload
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_success_valid_payload_validation_enabled(self):
         cfg.CONF.system.validate_trigger_payload = True
 
@@ -75,7 +80,33 @@ def test_dispatch_success_valid_payload_validation_enabled(self):
         self.assertEqual(self._dispatched_count, 1)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
+    @mock.patch('st2common.services.triggers.get_trigger_db_by_ref',
+                mock.MagicMock(return_value=TriggerDBMock(type='trigger-type-ref')))
+    def test_dispatch_success_with_validation_enabled_trigger_reference(self):
+        # Test a scenario where a Trigger ref and not TriggerType ref is provided
+        cfg.CONF.system.validate_trigger_payload = True
+
+        # define a valid payload
+        payload = {
+            'name': 'John Doe',
+            'age': 25,
+            'career': ['foo, Inc.', 'bar, Inc.'],
+            'married': True,
+            'awards': {'2016': ['hoge prize', 'fuga prize']},
+            'income': 50000
+        }
+
+        self.assertEqual(self._dispatched_count, 0)
+
+        # dispatching a trigger
+        self.sensor_service.dispatch('pack.86582f21-1fbc-44ea-88cb-0cd2b610e93b', payload)
+
+        # This assumed that the target tirgger dispatched
+        self.assertEqual(self._dispatched_count, 1)
+
+    @mock.patch('st2common.services.triggers.get_trigger_type_db',
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_success_with_validation_disabled_and_invalid_payload(self):
         """
         Tests that an invalid payload still results in dispatch success with default config
@@ -108,7 +139,7 @@ def test_dispatch_success_with_validation_disabled_and_invalid_payload(self):
         self.assertEqual(self._dispatched_count, 1)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_failure_caused_by_incorrect_type(self):
         # define a invalid payload (the type of 'age' is incorrect)
         payload = {
@@ -131,7 +162,7 @@ def test_dispatch_failure_caused_by_incorrect_type(self):
         self.assertEqual(self._dispatched_count, 1)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_failure_caused_by_lack_of_required_parameter(self):
         # define a invalid payload (lack of required property)
         payload = {
@@ -149,7 +180,7 @@ def test_dispatch_failure_caused_by_lack_of_required_parameter(self):
         self.assertEqual(self._dispatched_count, 1)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_failure_caused_by_extra_parameter(self):
         # define a invalid payload ('hobby' is extra)
         payload = {
@@ -162,7 +193,7 @@ def test_dispatch_failure_caused_by_extra_parameter(self):
         self.assertEqual(self._dispatched_count, 0)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_success_with_multiple_type_value(self):
         payload = {
             'name': 'John Doe',
@@ -180,7 +211,7 @@ def test_dispatch_success_with_multiple_type_value(self):
         self.assertEqual(self._dispatched_count, 2)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock(TEST_SCHEMA)))
+                mock.MagicMock(return_value=TriggerTypeDBMock(TEST_SCHEMA)))
     def test_dispatch_success_with_null(self):
         payload = {
             'name': 'John Doe',
@@ -193,7 +224,7 @@ def test_dispatch_success_with_null(self):
         self.assertEqual(self._dispatched_count, 1)
 
     @mock.patch('st2common.services.triggers.get_trigger_type_db',
-                mock.MagicMock(return_value=TriggerTypeMock()))
+                mock.MagicMock(return_value=TriggerTypeDBMock()))
     def test_dispatch_success_without_payload_schema(self):
         # the case trigger has no property
         self.sensor_service.dispatch('trigger-name', {})