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