Skip to content
5 changes: 5 additions & 0 deletions contrib/linux/sensors/file_watch_sensor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

import eventlet

from logshipper.tail import Tail

from st2reactor.sensor.base import Sensor
Expand Down Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an edge case in logshipper library.

If a trigger is added and add_file is called before the tailer is initialized, an exception will be thrown and the sensor will die / crash (this can happen because add_trigger method is called asynchronously and it could be called before run() and tailer fully initialized).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not ideal.

I could make it 100% deterministic by shuffling around the code (set initialized or similar variable to True inside run() and only proceed inside add_handler when that variable is True).

I think that's too complicated though since sleep seems to work just as fine (and it's one line of code).

It also only affects a single sensor right now so there is no point in handling that inside sensor container / base sensor class code.


self._tail.add_file(filename=file_path)
self._logger.info('Added file "%s"' % (file_path))

Expand Down
4 changes: 2 additions & 2 deletions st2common/st2common/services/trigger_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
21 changes: 19 additions & 2 deletions st2common/st2common/validators/api/reactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
53 changes: 42 additions & 11 deletions st2reactor/tests/unit/test_sensor_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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', {})
Expand Down