Skip to content

Commit 54e92c3

Browse files
authored
Merge pull request #14 from EncoreTechnologies/feature/slack-alerts-trigger
feature/slack-alerts-trigger
2 parents f3ede2a + 0c03ab4 commit 54e92c3

File tree

5 files changed

+196
-8
lines changed

5 files changed

+196
-8
lines changed

CHANGES.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change Log
22

3+
## v1.0.2
4+
5+
* Adds new trigger payload to track previous state
6+
7+
Contributed by Jacob Huff (Encore Technologies)
8+
39
## v1.0.1
410

511
* Enabling sensor in sensor parameters
@@ -18,7 +24,7 @@ Switches stackstorm-ci url to an ssh url
1824

1925
## v0.1.1
2026

21-
Fixes the following bugs/issues:
27+
Fixes the following bugs/issues:
2228
- executions where all tasks succeed but workflow fails were not detected
2329
- adds support for generic StackStorm errors like timouts
2430
- removes byte conversions to unicode

pack.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
ref: errors
33
name: errors
44
description: Error pack used to generate errors for tasks/workflows
5-
keywords:
5+
keywords:
66
- cookiecutter
77
- errors
8-
version: 1.0.1
8+
version: 1.0.2
99
author: Alex Chrystal
1010
1111
python_versions:

sensors/cron_sensor.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def __init__(self, sensor_service, config=None, poll_interval=None):
7171
poll_interval=poll_interval)
7272
self._logger = self._sensor_service.get_logger(__name__)
7373
self.trigger_ref = "errors.error_cron_event"
74+
self.enhanced_trigger_ref = "errors.error_cron_event_enhanced"
7475

7576
def setup(self):
7677
self.st2_fqdn = socket.getfqdn()
@@ -96,6 +97,9 @@ def poll(self):
9697
if not isinstance(self.kv_enforcements, dict):
9798
self.kv_enforcements = ast.literal_eval(self.kv_enforcements)
9899

100+
# Convert to new format temporarily for migration but keep using original format
101+
self._migrated_kv = self._migrate_kv_format(self.kv_enforcements)
102+
99103
self._logger.info("Current problem rules: {0}".format(self.kv_enforcements))
100104

101105
for rule in rules:
@@ -199,21 +203,33 @@ def dispatch_trigger(self,
199203
st2_comments="",
200204
st2_enforcement_id=None,
201205
st2_state="error"):
206+
previous_state = self._get_previous_state(st2_rule_name)
207+
202208
trigger_payload = {
203209
'st2_rule_name': st2_rule_name,
204210
'st2_server': st2_server,
205211
'st2_execution_id': st2_execution_id,
206212
'st2_comments': st2_comments,
207213
'st2_state': st2_state
208214
}
215+
216+
enhanced_trigger_payload = trigger_payload.copy()
217+
enhanced_trigger_payload['st2_previous_state'] = previous_state
218+
209219
if st2_state == "success":
210220
self._sensor_service.dispatch(trigger=self.trigger_ref, payload=trigger_payload)
221+
self._sensor_service.dispatch(trigger=self.enhanced_trigger_ref,
222+
payload=enhanced_trigger_payload)
211223
return True
212224

213225
if self.check_before_dispatch(st2_rule_name, st2_enforcement_id):
214226
self._logger.info("Sending trigger with payload: {0}".format(trigger_payload))
227+
self._logger.info("Sending enhanced trigger with previous state: {0}".format(
228+
previous_state))
215229

216230
self._sensor_service.dispatch(trigger=self.trigger_ref, payload=trigger_payload)
231+
self._sensor_service.dispatch(trigger=self.enhanced_trigger_ref,
232+
payload=enhanced_trigger_payload)
217233

218234
if st2_enforcement_id:
219235
self.kv_enforcements[st2_rule_name] = st2_enforcement_id
@@ -222,7 +238,7 @@ def dispatch_trigger(self,
222238

223239
else:
224240
self._logger.info("Already dispatched trigger. Waiting till another enforcement before"
225-
" dispatching another trigger.")
241+
" dispatching another trigger.")
226242

227243
return True
228244

@@ -280,6 +296,30 @@ def convert_to_crontab(self, st2_cron):
280296

281297
return " ".join(cron_tab)
282298

299+
def _migrate_kv_format(self, kv_data):
300+
""" Migrates old key-value format to new enhanced format
301+
Old: {'rule_name': 'enforcement_id'}
302+
New: {'rule_name': {'enforcement_id': 'id', 'previous_state': 'state'}}
303+
"""
304+
migrated_data = {}
305+
for rule_name, value in kv_data.items():
306+
if isinstance(value, dict):
307+
# Already in new format
308+
migrated_data[rule_name] = value
309+
else:
310+
# Old format - migrate
311+
migrated_data[rule_name] = {
312+
'enforcement_id': value,
313+
'previous_state': 'unknown'
314+
}
315+
return migrated_data
316+
317+
def _get_previous_state(self, rule_name):
318+
""" Gets the previous state for a rule """
319+
if rule_name in self._migrated_kv:
320+
return self._migrated_kv[rule_name].get('previous_state', 'unknown')
321+
return 'unknown'
322+
283323
def cleanup(self):
284324
pass
285325

sensors/cron_sensor.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,29 @@
3333
st2_state:
3434
type: "string"
3535
format: "The state that the error event should be in"
36+
-
37+
name: "error_cron_event_enhanced"
38+
description: "Enhanced trigger with previous state tracking for cron job state transitions"
39+
payload_schema:
40+
type: "object"
41+
properties:
42+
st2_comments:
43+
type: "string"
44+
format: "Comments on what is happening with the cron job."
45+
st2_execution_id:
46+
type: "string"
47+
format: "Stackstorm execution id"
48+
default: ""
49+
st2_rule_name:
50+
type: "string"
51+
format: "Stackstorm rule name"
52+
st2_server:
53+
type: "string"
54+
format: "Stackstorm server with the Cron error"
55+
st2_state:
56+
type: "string"
57+
format: "The current state that the error event should be in"
58+
st2_previous_state:
59+
type: "string"
60+
format: "The previous state of the cron job for tracking state transitions"
61+
default: "unknown"

tests/test_sensor_cron_sensor.py

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def test_check_enforcements_no_execution(self):
273273
enforced_at='2018-10-26T01:00:00.01Z',
274274
rule={'ref': 'test_rule'})
275275
mock_rule_enforcement = mock.Mock(id="test_id",
276-
failure_reason="test_failure")
276+
failure_reason="test_failure")
277277

278278
mock_st2_client = mock.MagicMock()
279279
mock_st2_client.ruleenforcements.get_by_id.return_value = mock_rule_enforcement
@@ -309,7 +309,7 @@ def test_check_enforcements_no_execution_jinja_escaping(self):
309309
enforced_at='2018-10-26T01:00:00.01Z',
310310
rule={'ref': 'test_rule'})
311311
mock_rule_enforcement = mock.Mock(id="test_id",
312-
failure_reason='{{ test_failure }}')
312+
failure_reason='{{ test_failure }}')
313313

314314
mock_st2_client = mock.MagicMock()
315315
mock_st2_client.ruleenforcements.get_by_id.return_value = mock_rule_enforcement
@@ -341,8 +341,8 @@ def test_check_enforcements_true(self):
341341
sensor.kv_enforcements = {}
342342

343343
mock_enforcement = mock.Mock(enforced_at='2018-10-26T01:00:00.01Z',
344-
execution_id='test',
345-
rule={'ref': 'test_rule'})
344+
execution_id='test',
345+
rule={'ref': 'test_rule'})
346346
mock_execution = mock.Mock(status='succeeded')
347347

348348
mock_st2_client = mock.MagicMock()
@@ -487,3 +487,119 @@ def test_convert_to_crontab_day_convert(self):
487487
expected_return = '* * * * * 4 *'
488488
result_value = sensor.convert_to_crontab(test_dict)
489489
self.assertEqual(result_value, expected_return)
490+
491+
# Enhanced functionality tests
492+
def test_migrate_kv_format_old_to_new(self):
493+
sensor = self.get_sensor_instance()
494+
old_format = {
495+
'rule1': 'enforcement_id_1',
496+
'rule2': 'error without enforcement id'
497+
}
498+
499+
result = sensor._migrate_kv_format(old_format)
500+
501+
expected = {
502+
'rule1': {'enforcement_id': 'enforcement_id_1', 'previous_state': 'unknown'},
503+
'rule2': {'enforcement_id': 'error without enforcement id', 'previous_state': 'unknown'}
504+
}
505+
self.assertEqual(result, expected)
506+
507+
def test_migrate_kv_format_already_new(self):
508+
sensor = self.get_sensor_instance()
509+
new_format = {
510+
'rule1': {'enforcement_id': 'enforcement_id_1', 'previous_state': 'error'}
511+
}
512+
513+
result = sensor._migrate_kv_format(new_format)
514+
self.assertEqual(result, new_format)
515+
516+
def test_get_previous_state(self):
517+
sensor = self.get_sensor_instance()
518+
sensor.kv_enforcements = {
519+
'rule1': {'enforcement_id': 'id1', 'previous_state': 'error'},
520+
'rule2': {'enforcement_id': 'id2'} # missing previous_state
521+
}
522+
523+
self.assertEqual(sensor._get_previous_state('rule1'), 'error')
524+
self.assertEqual(sensor._get_previous_state('rule2'), 'unknown')
525+
self.assertEqual(sensor._get_previous_state('nonexistent'), 'unknown')
526+
527+
def test_set_rule_state(self):
528+
sensor = self.get_sensor_instance()
529+
sensor.kv_enforcements = {}
530+
531+
sensor._set_rule_state('test_rule', 'test_enforcement', 'error')
532+
533+
expected = {'test_rule': {'enforcement_id': 'test_enforcement', 'previous_state': 'error'}}
534+
self.assertEqual(sensor.kv_enforcements, expected)
535+
536+
def test_enhanced_trigger_dispatch_error(self):
537+
sensor = self.get_sensor_instance()
538+
sensor.st2_fqdn = 'st2_test'
539+
sensor.kv_enforcements = {}
540+
541+
test_dict = {
542+
'st2_rule_name': 'test_rule',
543+
'st2_server': 'st2_test',
544+
'st2_execution_id': 'st2_test_execution',
545+
'st2_comments': 'test_comments',
546+
'st2_state': 'error'
547+
}
548+
549+
sensor.dispatch_trigger(**test_dict)
550+
551+
# Check both triggers were dispatched
552+
trigger_payload = {
553+
'st2_rule_name': 'test_rule',
554+
'st2_server': 'st2_test',
555+
'st2_execution_id': 'st2_test_execution',
556+
'st2_comments': 'test_comments',
557+
'st2_state': 'error'
558+
}
559+
560+
enhanced_trigger_payload = trigger_payload.copy()
561+
enhanced_trigger_payload['st2_previous_state'] = 'unknown'
562+
563+
self.assertTriggerDispatched(trigger='errors.error_cron_event',
564+
payload=trigger_payload)
565+
self.assertTriggerDispatched(trigger='errors.error_cron_event_enhanced',
566+
payload=enhanced_trigger_payload)
567+
568+
def test_enhanced_trigger_dispatch_success_with_previous_error(self):
569+
sensor = self.get_sensor_instance()
570+
sensor.st2_fqdn = 'st2_test'
571+
sensor.kv_enforcements = {'test_rule': {'enforcement_id': 'old_id',
572+
'previous_state': 'error'}}
573+
574+
test_dict = {
575+
'st2_rule_name': 'test_rule',
576+
'st2_server': 'st2_test',
577+
'st2_execution_id': 'new_execution',
578+
'st2_comments': 'Cronjob recovered',
579+
'st2_state': 'success',
580+
'st2_enforcement_id': 'new_enforcement'
581+
}
582+
583+
sensor.dispatch_trigger(**test_dict)
584+
585+
# Check both triggers were dispatched
586+
trigger_payload = {
587+
'st2_rule_name': 'test_rule',
588+
'st2_server': 'st2_test',
589+
'st2_execution_id': 'new_execution',
590+
'st2_comments': 'Cronjob recovered',
591+
'st2_state': 'success'
592+
}
593+
594+
enhanced_trigger_payload = trigger_payload.copy()
595+
enhanced_trigger_payload['st2_previous_state'] = 'error'
596+
597+
self.assertTriggerDispatched(trigger='errors.error_cron_event',
598+
payload=trigger_payload)
599+
self.assertTriggerDispatched(trigger='errors.error_cron_event_enhanced',
600+
payload=enhanced_trigger_payload)
601+
602+
# Verify state was updated
603+
expected_kv = {'test_rule': {'enforcement_id': 'new_enforcement',
604+
'previous_state': 'success'}}
605+
self.assertEqual(sensor.kv_enforcements, expected_kv)

0 commit comments

Comments
 (0)