Moved some files around and merged from 1.0 branch:
[monitor.git] / monitor / wrapper / mailer.py
1 #!/usr/bin/python2
2 #
3 # Copyright (c) 2004  The Trustees of Princeton University (Trustees).
4 #
5 # Faiyaz Ahmed <faiyaza@cs.princeton.edu>
6 #
7 # $Id: mailer.py,v 1.10 2007/08/08 13:28:06 soltesz Exp $
8 from emailTxt import *
9 import smtplib
10 from monitor import config
11 import calendar
12 import logging
13 import os
14 import time
15
16 logger = logging.getLogger("monitor")
17
18 MTA="localhost"
19 FROM=config.email
20
21 def reformat_for_rt(text):
22         lines = text.split("\n")
23         spaced_text = ""
24         for line in lines:
25                 spaced_text += " %s\n" %line
26         return spaced_text
27                 
28
29 def _setupRTenvironment():
30         os.environ['PATH'] = os.environ['PATH'] + ":" + config.RT_WEB_TOOLS_PATH
31         os.environ['RTSERVER'] = config.RT_WEB_SERVER
32         os.environ['RTUSER']   = config.RT_WEB_USER
33         os.environ['RTPASSWD'] = config.RT_WEB_PASSWORD
34         os.environ['RTDEBUG'] = config.RT_WEB_DEBUG
35         return
36
37 def setTicketStatus(ticket_id, status):
38         _setupRTenvironment()
39         if ticket_id == None or ticket_id == "":
40                 return {}
41
42         cmd = "rt edit ticket/%s set status=%s" % (ticket_id, status)
43         (f_in, f_out, f_err) = os.popen3(cmd)
44         value = f_out.read()
45         l_values = value.split('\n')
46         return "".join(l_values).strip()
47
48 def getTicketStatus(ticket_id):
49         _setupRTenvironment()
50         if ticket_id == None or ticket_id == "":
51                 return {}
52
53         cmd = "rt show -t ticket -f id,subject,status,queue,created %s" % (ticket_id)
54         (f_in, f_out, f_err) = os.popen3(cmd)
55         value = f_out.read()
56         l_values = value.split('\n')
57         r_values = {}
58         for line in l_values:
59                 if len(line) == 0: continue
60                 vals = line.split(':')
61                 key = vals[0]
62                 r_values[key] = ":".join(vals[1:])
63                 r_values[key] = r_values[key].strip()
64
65         r_values['Created'] = calendar.timegm(time.strptime(r_values['Created']))
66         return r_values
67
68 def setAdminCCViaRT(ticket_id, to):
69         # Set ENV Variables/PATH
70         _setupRTenvironment()
71         if ticket_id == None or ticket_id == "":
72                 raise Exception("ERROR: ticket_id must be set to some integer value")
73
74         # This will raise an exception if it is not a valid id.
75         i_ticket_id = int(ticket_id)
76
77         # create a comma-separated list
78         s_to = ",".join(to)
79         cmd = "rt edit ticket/%s set admincc='%s'" % (ticket_id, s_to)
80         (f_in, f_out, f_err) = os.popen3(cmd)
81         value = f_out.read()
82         l_values = value.split()
83         f_in.close() ; f_out.close() ; f_err.close()
84         if len(l_values) > 3 and "updated" in l_values[3]:
85                 # Success
86                 pass
87         else:
88                 print "VALUE:", value
89                 print "ERROR: RT failed to update AdminCC for ticket %s" % ticket_id
90
91         return
92
93 def setSubjectViaRT(ticket_id, subject):
94         # Set ENV Variables/PATH
95         _setupRTenvironment()
96         if ticket_id == None or ticket_id == "":
97                 raise Exception("ERROR: ticket_id must be set to some integer value")
98
99         # This will raise an exception if it is not a valid id.
100         i_ticket_id = int(ticket_id)
101
102         cmd = "rt edit ticket/%s set subject='%s'" % (ticket_id, subject)
103         (f_in, f_out, f_err) = os.popen3(cmd)
104         value = f_out.read()
105         l_values = value.split()
106         f_in.close() ; f_out.close() ; f_err.close()
107         if len(l_values) > 3 and "updated" in l_values[3]:
108                 # Success
109                 pass
110         else:
111                 print "VALUE:", value
112                 print "ERROR: RT failed to update subject for ticket %s" % ticket_id
113
114         return
115                 
116
117 def addCommentViaRT(ticket_id, comment):
118         # Set ENV Variables/PATH
119         _setupRTenvironment()
120         if ticket_id == None or ticket_id == "":
121                 raise Exception("ERROR: ticket_id must be set to some integer value")
122
123         # This will raise an exception if it is not a valid id.
124         i_ticket_id = int(ticket_id)
125
126         cmd = "rt comment -m '%s' ticket/%s" % (comment, i_ticket_id)
127         (f_in, f_out, f_err) = os.popen3(cmd)
128         value = f_out.read()
129         l_values = value.split()
130         f_in.close() ; f_out.close() ; f_err.close()
131         if len(l_values) > 1 and "recorded" in l_values[1]:
132                 # Success
133                 pass
134         else:
135                 # Error
136                 f_in.close() ; f_out.close() ; f_err.close()
137                 print "ERROR: RT failed to add comment to id %s" % ticket_id
138
139         return
140
141 def closeTicketViaRT(ticket_id, comment):
142         # Set ENV Variables/PATH
143         _setupRTenvironment()
144         if ticket_id == None or ticket_id == "":
145                 raise Exception("ERROR: ticket_id must be set to some integer value")
146
147         # This will raise an exception if it is not a valid id.
148         i_ticket_id = int(ticket_id)
149
150         # Append comment to RT ticket
151         addCommentViaRT(ticket_id, comment)
152
153         if not config.debug:
154                 cmd = "rt edit ticket/%s set status=resolved" % i_ticket_id
155                 (f_in, f_out, f_err) = os.popen3(cmd)
156                 f_in.close()
157                 value = f_out.read()
158                 f_out.close()
159                 f_err.close()
160                 l_values = value.split()
161                 if len(l_values) >= 4 and "updated" in l_values[3]:
162                         # Success!!
163                         pass
164                 else:
165                         print "VALUE: ", value
166                         # Failed!!
167                         print "FAILED to resolve Ticket %s" % ticket_id
168                         print "FAILED to resolve Ticket %s" % i_ticket_id
169
170         return
171
172 def emailViaRT(subject, text, to, ticket_id=None):
173         if ticket_id == None or ticket_id == "" or ticket_id == 0:
174                 print "No TICKET"
175                 return emailViaRT_NoTicket(subject, text, to)
176
177
178         # Set ENV Variables/PATH
179         _setupRTenvironment()
180
181         if config.mail and not config.debug:
182                 setSubjectViaRT(ticket_id, subject)
183                 setAdminCCViaRT(ticket_id, to)
184
185                 cmd = "rt correspond -m - %s" % ticket_id
186                 (f_in, f_out, f_err) = os.popen3(cmd)
187                 f_in.write(text)
188                 f_in.flush()
189                 f_in.close()
190                 value = f_out.read()
191
192                 # TODO: rt doesn't write to stderr on error!!!
193                 if value == "":
194                         raise Exception, f_err.read()
195
196                 del f_in
197                 f_out.close(); del f_out
198                 f_err.close(); del f_err
199                 os.wait()
200
201         return ticket_id
202         
203
204 def emailViaRT_NoTicket(subject, text, to):
205         """Use RT command line tools to send email.
206                 return the generated RT ticket ID number.
207         """
208         i_ticket = 0
209
210         if config.mail and config.debug:
211                 to = [config.email]
212
213         # Set ENV Variables/PATH
214         _setupRTenvironment()
215
216         # NOTE: AdminCc: (in PLC's RT configuration) gets an email sent.
217         # This is not the case (surprisingly) for Cc:
218         input_text  = "Subject: %s\n"
219         input_text += "Requestor: %s\n"% FROM
220         input_text += "id: ticket/new\n"
221         input_text += "Queue: %s\n" % config.RT_QUEUE
222         for recipient in to:
223                 input_text += "AdminCc: %s\n" % recipient
224         input_text += "Text: %s"
225
226         # Add a space for each new line to get RT to accept the file.
227         spaced_text = reformat_for_rt(text)
228
229         if config.mail and not config.debug:
230                 cmd = "rt create -i -t ticket"
231                 (f_in, f_out, f_err) = os.popen3(cmd)
232                 f_in.write(input_text % (subject, spaced_text))
233                 f_in.flush()
234                 f_in.close()
235                 value = f_out.read()
236
237                 # TODO: rt doesn't write to stderr on error!!!
238                 if value == "":
239                         raise Exception, f_err.read()
240
241                 print "MAILER: ticket value == %s" % value.split()[2]
242                 i_ticket = int(value.split()[2])
243                 # clean up the child process.
244                 f_in.close();  del f_in
245                 f_out.close(); del f_out
246                 f_err.close(); del f_err
247                 os.wait()
248         elif config.mail and config.debug:
249                 email(subject, spaced_text, to)
250                 i_ticket = 0
251         else:
252                 i_ticket = 0
253
254         return i_ticket
255
256 def email(subject, text, to):
257         """Create a mime-message that will render HTML in popular
258         MUAs, text in better ones"""
259         import MimeWriter
260         import mimetools
261         import cStringIO
262
263         if (config.mail and config.debug) or (not config.mail and not config.debug and config.bcc):
264                 to = [config.email]
265
266         out = cStringIO.StringIO() # output buffer for our message 
267         txtin = cStringIO.StringIO(text)
268
269         writer = MimeWriter.MimeWriter(out)
270         #
271         # set up some basic headers... we put subject here
272         # because smtplib.sendmail expects it to be in the
273         # message body
274         #
275         writer.addheader("Subject", subject)
276         if to.__class__ == [].__class__ :       
277                 writer.addheader("To", to[0])
278                 cc = ""
279                 for dest in to[1:len(to)]:
280                         cc +="%s, " % dest
281                 cc = cc.rstrip(", ") 
282                 writer.addheader("Cc", cc)
283         else:
284                 writer.addheader("To", to)
285
286         if config.bcc and not config.debug:
287                 writer.addheader("Bcc", config.email)
288
289         writer.addheader("Reply-To", FROM)
290                 
291         writer.addheader("MIME-Version", "1.0")
292         #
293         # start the multipart section of the message
294         # multipart/alternative seems to work better
295         # on some MUAs than multipart/mixed
296         #
297         writer.startmultipartbody("alternative")
298         writer.flushheaders()
299         #
300         # the plain text section
301         #
302         subpart = writer.nextpart()
303         subpart.addheader("Content-Transfer-Encoding", "quoted-printable")
304         pout = subpart.startbody("text/plain", [("charset", 'us-ascii')])
305         mimetools.encode(txtin, pout, 'quoted-printable')
306         txtin.close()
307         #
308         # Now that we're done, close our writer and
309         # return the message body
310         #
311         writer.lastpart()
312         msg = out.getvalue()
313         out.close()
314
315         # three cases:
316         #       mail but no-debug
317         #       mail and debug, 'to' changed at the beginning'
318         #   nomail, but report who I'd send to.
319         if config.mail:
320                 for mta in [MTA, 'golf.cs.princeton.edu']:
321                         try:
322                                 # This is normal operation
323                                 #print MTA
324                                 #print FROM
325                                 #print to
326                                 #print msg
327                                 server = smtplib.SMTP(mta)
328                                 #server = smtplib.SMTP('golf.cs.princeton.edu')
329                                 server.sendmail(FROM, to,  msg)
330                                 if config.bcc and not config.debug:
331                                         server.sendmail(FROM, config.email,  msg)
332                                 server.quit()
333                         except Exception, err:
334                                 print "Mailer error1: failed using MTA(%s) with: %s" % (mta, err)
335
336         elif not config.debug and not config.mail and config.bcc:
337                 for mta in [MTA, 'golf.cs.princeton.edu']:
338                         try:
339                                 server = smtplib.SMTP(mta)
340                                 server.sendmail(FROM, to,  msg)
341                                 server.quit()
342                         except Exception, err:
343                                 print "Mailer error2: failed using MTA(%s) with: %s" % (mta, err)
344         else:
345                 #print "Would mail %s" %to
346                 logger.debug("Would send mail to %s" % to)
347
348 if __name__=="__main__":
349         import smtplib
350         import emailTxt
351         import plc 
352         #email("[spam] bcc test from golf.cs.princeton.edu", 
353         #         "It gets to both recipients", 
354         #         "soltesz@cs.utk.edu")
355         #emailViaRT("rt via golf", 
356         #         "It gets to both recipients", 
357         #         "soltesz@cs.utk.edu")
358         email("Re: [PL #21323] TEST 7", 
359                            mailtxt.newbootcd_one[1] % {'hostname_list':"hostname list..."},
360                            [FROM])
361         #print "ticketid: %d" % id
362         #id = plc.siteId(["alice.cs.princeton.edu"])
363         #print id
364         #if id:
365                 #email('TEST', emailTxt.mailtxt.ssh % {'hostname': "ALICE.cs.princeton.edu"}, "tech-" + id + "@sites.planet-lab.org")
366         #else:
367         #       print "No dice."
368         #email("TEST111", "I'd like to see if this works anywhere", ["soltesz@cs.princeton.edu", "soltesz@cs.utk.edu"])
369         #print "mailer does nothing in main()"