Skip to content

Commit 01d6b8f

Browse files
task_events_mgr: include event message with event emails
* Closes #5566 * Note, the event message is not stored in the database, so when events are restored from the DB on restart, the event message will default to the event name.
1 parent 2fbcf49 commit 01d6b8f

File tree

8 files changed

+102
-39
lines changed

8 files changed

+102
-39
lines changed

changes.d/5769.feat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Include task messages and workflow port as appropriate in emails configured by "mail events".

cylc/flow/id.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ def __eq__(self, other):
163163
for key in self._KEYS
164164
)
165165

166+
def __lt__(self, other):
167+
return self.id < other.id
168+
169+
def __gt__(self, other):
170+
return self.id > other.id
171+
166172
def __ne__(self, other):
167173
if not isinstance(other, self.__class__):
168174
return True

cylc/flow/task_events_mgr.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ class EventKey(NamedTuple):
126126
"""The task event."""
127127
event: str
128128

129+
"""The task event message.
130+
131+
Warning: This information is not currently preserved in the DB so will be
132+
lost on restart.
133+
"""
134+
message: str
135+
129136
"""The job tokens."""
130137
tokens: 'Tokens'
131138

@@ -897,7 +904,7 @@ def setup_event_handlers(self, itask, event, message):
897904
msg = message
898905
self._db_events_insert(itask, event, msg)
899906
self._setup_job_logs_retrieval(itask, event)
900-
self._setup_event_mail(itask, event)
907+
self._setup_event_mail(itask, event, message)
901908
self._setup_custom_event_handlers(itask, event, message)
902909

903910
def _custom_handler_callback(
@@ -951,14 +958,18 @@ def _process_event_email(
951958
subject = "[%d task events] %s" % (
952959
len(id_keys), schd.workflow)
953960
cmd = ["mail", "-s", subject]
961+
954962
# From: and To:
955963
cmd.append("-r")
956964
cmd.append(ctx.mail_from)
957965
cmd.append(ctx.mail_to)
966+
958967
# STDIN for mail, tasks
959968
stdin_str = ""
960969
for id_key in sorted(id_keys):
961-
stdin_str += f'{id_key.event}: {id_key.tokens.relative_id}\n'
970+
stdin_str += f'job: {id_key.tokens.relative_id}\n'
971+
stdin_str += f'event: {id_key.event}\n'
972+
stdin_str += f'message: {id_key.message}\n\n'
962973

963974
# STDIN for mail, event info + workflow detail
964975
stdin_str += "\n"
@@ -1491,6 +1502,7 @@ def _setup_job_logs_retrieval(self, itask, event) -> None:
14911502
id_key = EventKey(
14921503
self.HANDLER_JOB_LOGS_RETRIEVE,
14931504
event,
1505+
event,
14941506
itask.tokens.duplicate(job=itask.submit_num),
14951507
)
14961508
if id_key in self._event_timers:
@@ -1514,7 +1526,12 @@ def _setup_job_logs_retrieval(self, itask, event) -> None:
15141526
)
15151527
)
15161528

1517-
def _setup_event_mail(self, itask: 'TaskProxy', event: str) -> None:
1529+
def _setup_event_mail(
1530+
self,
1531+
itask: 'TaskProxy',
1532+
event: str,
1533+
message: str,
1534+
) -> None:
15181535
"""Set up task event notification, by email."""
15191536
if event not in self._get_events_conf(itask, "mail events", []):
15201537
# event does not need to be processed
@@ -1523,6 +1540,7 @@ def _setup_event_mail(self, itask: 'TaskProxy', event: str) -> None:
15231540
id_key = EventKey(
15241541
self.HANDLER_MAIL,
15251542
get_event_id(event, itask),
1543+
message,
15261544
itask.tokens.duplicate(job=itask.submit_num),
15271545
)
15281546
if id_key in self._event_timers:
@@ -1571,6 +1589,7 @@ def _setup_custom_event_handlers(
15711589
id_key = EventKey(
15721590
f'{self.HANDLER_CUSTOM}-{i:02d}',
15731591
get_event_id(event, itask),
1592+
message,
15741593
itask.tokens.duplicate(job=itask.submit_num),
15751594
)
15761595

cylc/flow/task_pool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,9 @@ def load_db_task_action_timers(self, row_idx, row) -> None:
689689
EventKey(
690690
handler,
691691
event,
692+
# NOTE: the event "message" is not preserved in the DB so
693+
# we use the event as a placeholder
694+
event,
692695
tokens.duplicate(job=submit_num),
693696
),
694697
TaskActionTimer(

tests/functional/events/09-task-event-mail.t

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
if ! command -v mail 2>'/dev/null'; then
2121
skip_all '"mail" command not available'
2222
fi
23-
set_test_number 5
23+
set_test_number 6
2424
mock_smtpd_init
2525
OPT_SET=
2626
if [[ "${TEST_NAME_BASE}" == *-globalcfg ]]; then
@@ -49,17 +49,15 @@ run_ok "${TEST_NAME_BASE}-validate" \
4949
workflow_run_ok "${TEST_NAME_BASE}-run" \
5050
cylc play --reference-test --debug --no-detach ${OPT_SET} "${WORKFLOW_NAME}"
5151

52-
contains_ok "${TEST_SMTPD_LOG}" <<__LOG__
53-
retry: 1/t1/01
54-
succeeded: 1/t1/02
55-
see: http://localhost/stuff/${USER}/${WORKFLOW_NAME}/
56-
__LOG__
52+
run_ok "${TEST_NAME_BASE}-grep-log-1" \
53+
grep -Pizo 'job: 1/t1/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
54+
run_ok "${TEST_NAME_BASE}-grep-log-2" \
55+
grep -Pizo 'job: 1/t1/02.*\n.*event: succeeded' "${TEST_SMTPD_LOG}"
5756

58-
59-
run_ok "${TEST_NAME_BASE}-grep-log" \
60-
grep -qPizo "Subject: \[1/t1/01 retry\]\n? ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
61-
run_ok "${TEST_NAME_BASE}-grep-log" \
62-
grep -qPizo "Subject: \[1/t1/02 succeeded\]\n? ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
57+
run_ok "${TEST_NAME_BASE}-grep-log-3" \
58+
grep -Pizo "Subject: \[1/t1/01 retry\].*(\n)?.* ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
59+
run_ok "${TEST_NAME_BASE}-grep-log-4" \
60+
grep -Pizo "Subject: \[1/t1/02 succeeded\].*(\n)?.* ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
6361

6462
purge
6563
mock_smtpd_kill

tests/functional/events/29-task-event-mail-1.t

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
if ! command -v mail 2>'/dev/null'; then
2121
skip_all '"mail" command not available'
2222
fi
23-
set_test_number 4
23+
set_test_number 5
2424

2525
mock_smtpd_init
2626
create_test_global_config "
@@ -37,13 +37,15 @@ run_ok "${TEST_NAME_BASE}-validate" \
3737
workflow_run_ok "${TEST_NAME_BASE}-run" \
3838
cylc play --reference-test --debug --no-detach "$WORKFLOW_NAME"
3939

40-
contains_ok "${TEST_SMTPD_LOG}" <<__LOG__
41-
retry: 1/t1/01
42-
see: http://localhost/stuff/${USER}/${WORKFLOW_NAME}/
43-
__LOG__
40+
run_ok "${TEST_NAME_BASE}-grep-log-1" \
41+
grep -Pizo "job: 1/t1/01.*\n.*event: retry.*\n.*" "${TEST_SMTPD_LOG}"
4442

45-
run_ok "${TEST_NAME_BASE}-grep-log" \
46-
grep -qPizo "Subject: \[1/t1/01 retry\]\n? ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
43+
run_ok "${TEST_NAME_BASE}-grep-log-2" grep \
44+
"see: http://localhost/stuff/${USER}/${WORKFLOW_NAME}/" \
45+
"${TEST_SMTPD_LOG}"
46+
47+
run_ok "${TEST_NAME_BASE}-grep-log-2" \
48+
grep -Pizo "Subject: \\[1/t1/01 retry\\].*(\n)?.*${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
4749

4850
purge
4951
mock_smtpd_kill

tests/functional/events/30-task-event-mail-2.t

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
if ! command -v mail 2>'/dev/null'; then
2121
skip_all '"mail" command not available'
2222
fi
23-
set_test_number 5
23+
set_test_number 20
2424
mock_smtpd_init
2525
OPT_SET=
2626
if [[ "${TEST_NAME_BASE}" == *-globalcfg ]]; then
@@ -49,22 +49,29 @@ run_ok "${TEST_NAME_BASE}-validate" \
4949
workflow_run_fail "${TEST_NAME_BASE}-run" \
5050
cylc play --reference-test --debug --no-detach ${OPT_SET} "${WORKFLOW_NAME}"
5151

52+
# 1 - retry
53+
run_ok "${TEST_NAME_BASE}-t1-01" grep -Pizo 'job: 1/t1/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
54+
run_ok "${TEST_NAME_BASE}-t2-01" grep -Pizo 'job: 1/t2/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
55+
run_ok "${TEST_NAME_BASE}-t3-01" grep -Pizo 'job: 1/t3/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
56+
run_ok "${TEST_NAME_BASE}-t4-01" grep -Pizo 'job: 1/t4/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
57+
run_ok "${TEST_NAME_BASE}-t5-01" grep -Pizo 'job: 1/t5/01.*\n.*event: retry' "${TEST_SMTPD_LOG}"
58+
59+
# 2 - retry
60+
run_ok "${TEST_NAME_BASE}-t1-02" grep -Pizo 'job: 1/t1/02.*\n.*event: retry' "${TEST_SMTPD_LOG}"
61+
run_ok "${TEST_NAME_BASE}-t2-02" grep -Pizo 'job: 1/t2/02.*\n.*event: retry' "${TEST_SMTPD_LOG}"
62+
run_ok "${TEST_NAME_BASE}-t3-02" grep -Pizo 'job: 1/t3/02.*\n.*event: retry' "${TEST_SMTPD_LOG}"
63+
run_ok "${TEST_NAME_BASE}-t4-02" grep -Pizo 'job: 1/t4/02.*\n.*event: retry' "${TEST_SMTPD_LOG}"
64+
run_ok "${TEST_NAME_BASE}-t5-02" grep -Pizo 'job: 1/t5/02.*\n.*event: retry' "${TEST_SMTPD_LOG}"
65+
66+
# 3 - fail
67+
run_ok "${TEST_NAME_BASE}-t1-03" grep -Pizo 'job: 1/t1/03.*\n.*event: failed' "${TEST_SMTPD_LOG}"
68+
run_ok "${TEST_NAME_BASE}-t2-03" grep -Pizo 'job: 1/t2/03.*\n.*event: failed' "${TEST_SMTPD_LOG}"
69+
run_ok "${TEST_NAME_BASE}-t3-03" grep -Pizo 'job: 1/t3/03.*\n.*event: failed' "${TEST_SMTPD_LOG}"
70+
run_ok "${TEST_NAME_BASE}-t4-03" grep -Pizo 'job: 1/t4/03.*\n.*event: failed' "${TEST_SMTPD_LOG}"
71+
run_ok "${TEST_NAME_BASE}-t5-03" grep -Pizo 'job: 1/t5/03.*\n.*event: failed' "${TEST_SMTPD_LOG}"
72+
73+
5274
contains_ok "${TEST_SMTPD_LOG}" <<__LOG__
53-
retry: 1/t1/01
54-
retry: 1/t2/01
55-
retry: 1/t3/01
56-
retry: 1/t4/01
57-
retry: 1/t5/01
58-
retry: 1/t1/02
59-
retry: 1/t2/02
60-
retry: 1/t3/02
61-
retry: 1/t4/02
62-
retry: 1/t5/02
63-
failed: 1/t1/03
64-
failed: 1/t2/03
65-
failed: 1/t3/03
66-
failed: 1/t4/03
67-
failed: 1/t5/03
6875
see: http://localhost/stuff/${USER}/${WORKFLOW_NAME}/
6976
__LOG__
7077

@@ -73,6 +80,6 @@ run_ok "${TEST_NAME_BASE}-grep-log" \
7380
run_ok "${TEST_NAME_BASE}-grep-log" \
7481
grep -qPizo "Subject: \[. tasks failed\]\n? ${WORKFLOW_NAME}" "${TEST_SMTPD_LOG}"
7582

76-
purge
83+
purge
7784
mock_smtpd_kill
7885
exit

tests/integration/events/test_task_events.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def test_mail_footer_template(
5151

5252
# start the workflow and get it to send an email
5353
ctx = SimpleNamespace(mail_to=None, mail_from=None)
54-
id_keys = [EventKey('none', 'failed', Tokens('//1/a'))]
54+
id_keys = [EventKey('none', 'failed', 'failed', Tokens('//1/a'))]
5555
async with start(mod_one) as one_log:
5656
mod_one.task_events_mgr._process_event_email(mod_one, ctx, id_keys)
5757

@@ -72,5 +72,32 @@ async def test_mail_footer_template(
7272
assert len(mail_calls) == 1
7373

7474

75+
async def test_event_email_body(
76+
mod_one,
77+
start,
78+
capcall,
79+
):
80+
"""It should send an email with the event context."""
81+
mail_calls = capcall(
82+
'cylc.flow.task_events_mgr.TaskEventsManager._send_mail'
83+
)
84+
85+
# start the workflow and get it to send an email
86+
ctx = SimpleNamespace(mail_to=None, mail_from=None)
87+
async with start(mod_one):
88+
# send a custom task message with the warning severity level
89+
id_keys = [EventKey('none', 'warning', 'warning message', Tokens('//1/a/01'))]
90+
mod_one.task_events_mgr._process_event_email(mod_one, ctx, id_keys)
91+
92+
# test the email which would have been sent for this message
93+
email_body = mail_calls[0][0][3]
94+
assert 'event: warning'
95+
assert 'job: 1/a/01' in email_body
96+
assert 'message: warning message' in email_body
97+
assert f'workflow: {mod_one.tokens["workflow"]}' in email_body
98+
assert f'host: {mod_one.host}' in email_body
99+
assert f'port: {mod_one.server.port}' in email_body
100+
assert f'owner: {mod_one.owner}' in email_body
101+
75102
# NOTE: we do not test custom event handlers here because these are tested
76103
# as a part of workflow validation (now also performed by cylc play)

0 commit comments

Comments
 (0)