1#!/usr/bin/env python3
2
3"""Event email functionality.
4
5Contains handler to test whether emails should be for each ingested
6event.
7
8The is pre-Roles code from before the User/Role classes were added."""
9
10import logging
11from datetime import datetime
12
13from chart.project import settings
14from chart.db.connection import db_connect
15from chart.common import sendmail
16from chart.common.prettyprint import show_time_ms
17from chart.common.prettyprint import show_time
18from chart.common.util import nvl
19from chart.common.xml import datetime_to_xml
20from chart.common.xml import xml_to_datetime
21from chart.common.util import ensure_dir_exists
22
23db_conn = db_connect() # TBD: used to auth_user and users
24
25logger = logging.getLogger()
26
27
28def get_notify_dir():
29 """Return directory for notification statefiles,
30 creating it if needed."""
31
32 res = settings.STATEFILE_DIR.joinpath('notifications')
33 ensure_dir_exists(res)
34 return res
35
36
37def event_notify(event, sendmails=False, cutoff_duration=settings.EMAIL_CUTOFF):
38 """Tell any subscribers about a new event.
39 This is called on ingestion and sends an email to every user who is subscribed to this event.
40 If the event is older than settings.EMAIL_CUTOFF don't send it, unless
41 `force_sendmail` is set.
42 """
43 if cutoff_duration is None:
44 cutoff_duration = settings.EMAIL_CUTOFF
45
46 if cutoff_duration is not None and (
47 (not sendmails) or (event.start_time < (datetime.utcnow() - cutoff_duration))):
48 return
49
50 # if the event is configured to use the EMAIL_NOTIFICATIONS table, block it
51 # if there is already a notification present
52 if event.event_class.email_notification:
53 # build a filename for a notification file in the statefiles directory
54 state_filename = get_notify_dir().joinpath('EVENT_{event}_{sid}'.format(
55 event=event.event_classname,
56 sid=event.sid.name))
57
58 if state_filename.exists():
59 # state is already set
60 logger.info('Not sending emails as a notification exists in {name}'.format(
61 name=state_filename))
62 return
63
64 # write an empty state file
65 with open(str(state_filename), 'w') as handle:
66 handle.write(datetime_to_xml(event.start_time))
67
68 logger.info('Created an email event notification statefile {name}'.format(
69 name=state_filename))
70
71 # logger.debug('Event emailer received {e}'.format(e=event))
72 for first_name, last_name, email in db_conn.query(
73 'SELECT first_name, last_name, email '
74 'FROM auth_user usr, event_subscriptions sub '
75 'WHERE usr.id=sub.django_user_id '
76 'AND sub.event_classname=:event_classname '
77 'AND {sid}'.format(sid=event.sid.sql_where(table_name='EVENT_SUBSCRIPTIONS',
78 match_on_none=True,
79 prefix='sub.')),
80 event_classname=event.event_classname):
81
82 # if sim_sendmail:
83 # logger.info('Would send email to {first} {last} <{email}>'.format(
84 # first=first_name, last=last_name, email=email))
85 email_event(first_name, last_name, email, event)
86
87
88def email_event(first_name=None, last_name=None, email=None, event=None):
89 """Send an event notification email to a single person."""
90 if first_name is not None:
91 name = '{first} {last}'.format(first=nvl(first_name, ''), last=nvl(last_name, ''))
92 logger.debug('Emailing {name} {email}'.format(name=name, email=email))
93 to_address = (name, email)
94
95 else:
96 # documentation claims "to" address can either be email or (name, email)
97 # but it requires a tuple
98 to_address = (email, email)
99
100 times = 'Start time: {time}'.format(time=show_time_ms(event.start_time))
101 if event.stop_time is not None:
102 times += '\nStop time: {time}'.format(time=show_time_ms(event.stop_time))
103
104 # if not None, this function will return a message body to replace the default one
105 email_function = event.event_class.email_function
106
107 if email_function is not None:
108 message = email_function(event)
109
110 else:
111 message = """{APPNAME} has raised an event of class {classname}
112
113Source: {sid}
114{times}
115
116{desc}
117
118This event was produced by the script {gen_method} at {gen_time} and the event id is {event_id}.
119Do not reply to this email.
120{suffix}""".format(
121 APPNAME=settings.APPNAME,
122 classname=event.event_classname,
123 sid=event.sid,
124 times=times,
125 desc=event.description('email').encode('utf-8'),
126 gen_method=event.gen_method,
127 gen_time=event.gen_time,
128 event_id=-1 if event.event_id is None else event.event_id,
129 suffix=settings.EMAIL_MESSAGE_SUFFIX)
130
131 sendmail.sendmail(
132 from_address=(settings.EMAIL_NAME.format('system'),
133 settings.EMAIL_ADDRESS.format('system')),
134 to_addresses=(to_address,),
135 subject='{prefix}{classname} event generated for {sid}'.format(
136 prefix=settings.EMAIL_SUBJECT_PREFIX,
137 classname=event.event_classname,
138 sid=event.sid.name),
139 message=message)
140
141
142def event_denotify(event_classname,
143 sid=None,
144 stop_time=None,
145 cutoff_duration=settings.EMAIL_CUTOFF):
146 """An event-raising algorithm can call this function to indicate a certain event
147 did not occur. This will result in an email notification to subscribers.
148 """
149 if stop_time < (datetime.utcnow() - cutoff_duration):
150 return
151
152 # build a filename for a notification file in the statefiles directory
153 state_filename = get_notify_dir().joinpath('EVENT_{event}_{sid}'.format(
154 event=event_classname,
155 sid=sid.name))
156
157 if state_filename.exists():
158 # state is already set
159 with open(str(state_filename), 'r') as handle:
160 start_time = xml_to_datetime(handle.read().strip())
161
162 logger.debug('Deleting existing event notification {name}'.format(
163 name=state_filename))
164 # there is an activate notification for this event/scid combination
165 state_filename.unlink()
166
167 for first_name, last_name, email in db_conn.query(
168 'SELECT first_name, last_name, email '
169 'FROM auth_user usr, event_subscriptions sub '
170 'WHERE usr.id=sub.django_user_id '
171 'AND sub.event_classname=:event_classname '
172 'AND (sub.scid is null or sub.scid=:scid)',
173 event_classname=event_classname,
174 scid=sid.sid.name):
175
176 logger.debug('Emailing {first} {last}'.format(first=first_name, last=last_name))
177
178 message = """The following condition has now ended:
179
180Event name: {classname}
181Source: {sid}
182Start time: {start}
183Stop time: {stop}
184
185Do not reply to this email.
186{suffix}""".format(
187 classname=event_classname,
188 sid=sid,
189 start=show_time(start_time) if start_time is not None else 'unknown',
190 stop=show_time(stop_time) if stop_time is not None else 'unknown',
191 suffix=settings.EMAIL_MESSAGE_SUFFIX)
192
193 sendmail.sendmail(from_address=(settings.EMAIL_NAME.format('system'),
194 settings.EMAIL_ADDRESS.format('system')),
195 to_addresses=((first_name + ' ' + last_name, email),),
196 subject='CHART ' + event_classname + ' denotification ' + str(sid),
197 message=message)
198
199 logger.debug('All done')