changes for 3.0
[monitor.git] / rt.py
1 #!/usr/bin/python2
2
3 import os, sys, shutil
4 import MySQLdb
5 import string
6 import logging
7 import Queue
8 import time 
9 import re
10 import database
11 from threading import *
12 import config
13
14 # TODO: merge the RT mailer from mailer.py into this file.
15
16 #Logging
17 logger = logging.getLogger("monitor")
18
19 # seconds between ticket update
20 RTSLEEP = 7200
21
22 def stripQuotes( str ):
23         quotes= ["'",'"']
24         if str[0] in quotes:
25                 str= str[1:]
26         if str[len(str)-1] in quotes:
27                 str= str[:len(str)-1]
28         return str
29
30
31 def readConstantsFile( file_path ):
32         """
33         read a file consisting of lines of
34         NAME=VALUE
35         NAME='VALUE'
36         NAME="VALUE"
37         and return a dictionary of the values.
38
39         blank lines, and lines starting with # (comments) are skipped
40         """
41
42         contents= {}
43
44         try:
45                 input_file= file(file_path,"r")
46         except IOError, err:
47                 return None
48
49         for line in input_file:
50                 if line[0] == "#":
51                         continue
52                 line= string.strip(line)
53                 if len(line) == 0:
54                         continue
55
56                 parts= string.split(line,"=",)
57                 if len(parts) <> 2:
58                         continue
59
60                 contents[parts[0]]= stripQuotes(parts[1])
61
62         return contents
63
64
65
66 def open_rt_db():
67
68         # read plc database passwords and connect
69         #rt_db_constants= readConstantsFile(RT_DB_CONSTANTS_PATH)
70         #if rt_db_constants is None:
71         #       print "Unable to read database access constants from %s" % \
72         #                 RT_DB_CONSTANTS_PATH
73         #       return -1
74
75         try:
76                 rt_db = MySQLdb.connect(host=config.RT_DB_HOST,
77                                                                 user=config.RT_DB_USER,
78                                                                 passwd=config.RT_DB_PASSWORD,
79                                                                 db=config.RT_DB_NAME)
80         except Exception, err:
81                 print "Failed to connect to RT database: %s" %err
82                 return -1
83
84         return rt_db
85
86
87
88 def fetch_from_db(db, sql):
89         try:
90                 # create a 'cursor' (required by MySQLdb)
91                 c = db.cursor()
92                 c.execute(sql)
93         except Exception, err:
94                 print "Could not execute RT query %s" %err
95                 return -1
96
97         # fetch all rows (list of lists)
98         raw = c.fetchall()
99         return raw
100         
101
102 def rt_tickets():
103         db = open_rt_db()
104         if db == -1:
105                 return ""
106 #       sql = """SELECT distinct Tk.id, Tk.Status, Tk.Subject
107 #                        FROM Tickets AS Tk
108 #                        JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId
109 #                        JOIN Attachments AS At ON Tr.id=At.TransactionID
110 #                        WHERE (At.Content LIKE '%%%s%%' OR
111 #                               At.Subject LIKE '%%%s%%') AND
112 #                               (Tk.Status = 'new' OR Tk.Status = 'open') AND
113 #                               Tk.Queue = 3 OR Tk.Queue = 19 
114 #                        ORDER BY Tk.Status, Tk.LastUpdated DESC""" \
115 #                        % (hostname,hostname)
116 #       sql = """SELECT distinct Tk.id, Tk.Status, Tk.Subject
117 #                        FROM Tickets AS Tk
118 #                        JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId
119 #                        JOIN Attachments AS At ON Tr.id=At.TransactionID
120 #                        WHERE (At.Content LIKE '%%%s%%' OR
121 #                               At.Subject LIKE '%%%s%%') AND
122 #                               (Tk.Status = 'new' OR Tk.Status = 'open')
123 #                        ORDER BY Tk.Status, Tk.LastUpdated DESC""" \
124 #                        % (hostname,hostname)
125
126         # Queue == 10 is the spam Queue in RT.
127 # SELECT Tk.* FROM Tickets AS Tk, Attachments AS At JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId  WHERE Tk.Queue != 10 AND Tk.id > 10000 AND Tr.id=At.TransactionID AND Tk.Status = 'open' ;
128
129
130         sql = """SELECT distinct Tk.id, Tk.Status, Tk.Subject, At.Content
131                          FROM Tickets AS Tk, Attachments AS At 
132                          JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId  
133                          WHERE Tk.Queue != 10 AND Tk.id > 10000 AND 
134                                    Tr.id=At.TransactionID AND Tk.Status = 'open'"""
135                                    #Tr.id=At.TransactionID AND (Tk.Status = 'new' OR Tk.Status = 'open')"""
136         #sqlall = """SELECT distinct Tk.id, Tk.Status, Tk.Subject, At.Content
137 #FROM Tickets AS Tk, Attachments AS At 
138 #JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId  
139 #WHERE Tk.Queue != 10 AND Tk.id > 10000 AND 
140 #Tr.id=At.TransactionID AND ( Tk.Status = 'open' OR
141 #Tk.Status = 'new') """
142         sqlall = """SELECT distinct Tk.id, Tk.Status, Tk.Subject, At.Content, Us.EmailAddress, Tk.LastUpdated, Q.Name, Tk.Owner FROM Tickets AS Tk, Attachments AS At, Queues as Q, Users as Us JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId WHERE (Tk.Queue=3 OR Tk.Queue=22) AND Tk.id > 10000 AND Tr.id=At.TransactionID AND ( Tk.Status = 'open' OR Tk.Status = 'new') AND Us.id=Tk.LastUpdatedBy AND Q.id=Tk.Queue """
143
144
145         raw = fetch_from_db(db, sql)
146         if raw == -1:
147                 return raw
148         tickets = map(lambda x: {"ticket_id":str(x[0]),
149                                 "status":x[1],
150                                 "subj":str(x[2]),
151                                 "content":str(x[3])},
152                                 raw)
153
154         raw = fetch_from_db(db,sqlall)
155         if raw == -1:
156                 return raw
157         tickets_all = map(lambda x: {"ticket_id":str(x[0]),
158                                 "status":x[1],
159                                 "subj":str(x[2]),
160                                 "content":str(x[3]),
161                                 "email":str(x[4]),
162                                 "lastupdated":str(x[5]),
163                                 "queue":str(x[6]),
164                                 "owner":str(x[7]),
165                                 },
166                                 raw)
167
168         db.close()
169
170         idTickets = {}
171         for t in tickets_all:
172                 idTickets[t['ticket_id']] = t
173         database.dbDump("idTickets", idTickets)
174
175         return tickets
176
177 def is_host_in_rt_tickets(host, ticket_blacklist, ad_rt_tickets):
178         # ad_rt_tickets is an array of dicts, defined above.
179         if len(ad_rt_tickets) == 0:
180                 return (False, None)
181         
182         d_ticket = ad_rt_tickets[0]
183         if not ('ticket_id' in d_ticket and 'status' in d_ticket and 
184                         'subj' in d_ticket and 'content' in d_ticket):
185                 logger.debug("RT_tickets array has wrong fields!!!")
186                 return (False, None)
187
188         #logger.debug("Searching all tickets for %s" % host)
189         def search_tickets(host, ad_rt_tickets):
190                 # compile once for more efficiency
191                 re_host = re.compile(host)
192                 for x in ad_rt_tickets:
193                         if re_host.search(x['subj'], re.MULTILINE|re.IGNORECASE) or \
194                            re_host.search(x['content'], re.MULTILINE|re.IGNORECASE):
195                                 logger.debug("\t ticket %s has %s" % (x['ticket_id'], host))
196                                 print "\t ticket %s has %s" % (x['ticket_id'], host)
197                                 if x['ticket_id'] in ticket_blacklist:
198                                         return (False, x)
199                                 else:
200                                         return (True, x)
201                 print "\t noticket -- has %s" % host
202                 #logger.debug("\t noticket -- has %s" % host)
203                 return (False, None)
204
205         # This search, while O(tickets), takes less than a millisecond, 05-25-07
206         #t = commands.MyTimer()
207         ret = search_tickets(host, ad_rt_tickets)
208         #del t
209
210         return ret
211
212
213 '''
214 Finds tickets associated with hostnames.
215 The idea is if you give it an array of host names,
216 presumeably from comon's list of bad nodes, it starts
217 a few threads to query RT.  RT takes a while to return.
218
219 This is turning into a reinvention of DB design, which I dont believe in.
220 In an effort to keep things minimal, here's the basic algo:
221
222 Give list of hostnames to RT()
223 Finds tickets associate with new hostnames (not in dict(tickets)).
224 Remove nodes that have come backup. Don't care of ticket is closed after first query.
225 Another thread refresh tickets of nodes already in dict and remove nodes that have come up. 
226 '''
227 class RT(Thread):
228         def __init__(self, dbTickets, q_toRT, q_fromRT, l_ticket_blacklist, target = None): 
229                 # Time of last update of ticket DB
230                 self.dbTickets = dbTickets
231                 self.lastupdated = 0
232                 self.l_ticket_blacklist = l_ticket_blacklist
233                 self.q_toRT = q_toRT
234                 self.q_fromRT = q_fromRT 
235                 self.tickets = {}
236                 Thread.__init__(self,target = self.getTickets)
237
238         # Takes node from q_toRT, gets tickets.  
239         # Thread that actually gets the tickets.
240         def getTickets(self):
241                 self.count = 0
242                 while 1:
243                         diag_node = self.q_toRT.get(block = True)
244                         if diag_node != None: 
245                                 host = diag_node['nodename']
246                                 (b_host_inticket, r_ticket) = is_host_in_rt_tickets(host, \
247                                                                                                         self.l_ticket_blacklist, \
248                                                                                                         self.dbTickets)
249                                 diag_node['found_rt_ticket'] = None
250                                 if b_host_inticket:
251                                         logger.debug("RT: found tickets for %s" %host)
252                                         diag_node['found_rt_ticket'] = r_ticket['ticket_id']
253
254                                 else:
255                                         if r_ticket is not None:
256                                                 print "Ignoring ticket %s" % r_ticket['ticket_id']
257                                                 # TODO: why do i return the ticket id for a
258                                                 #               blacklisted ticket id?
259                                                 #diag_node['found_rt_ticket'] = r_ticket['ticket_id']
260                                         self.count = self.count + 1
261
262                                 self.q_fromRT.put(diag_node) 
263                         else:
264                                 print "RT processed %d nodes with noticket" % self.count
265                                 logger.debug("RT filtered %d noticket nodes" % self.count)
266                                 self.q_fromRT.put(None)
267
268                                 break
269
270         # Removes hosts that are no longer down.
271         def remTickets(self):
272                 logger.debug("Removing stale entries from DB.") 
273                 prevdown = self.tickets.keys()
274
275                 currdown = []
276                 ##BEGIN HACK.  This should be outside of this file. passed to class.
277                 #cmn = comon.Comon(None, None)
278         #       cmn.updatebkts()
279                 #for bucket in cmn.comonbkts.keys():
280                 #       for host in getattr(cmn,bucket):
281                 #               if host not in currdown: currdown.append(host)
282                 ##END HACK
283
284                 # Actually do the comparison
285                 #for host in prevdown:
286                 #       if host not in currdown:
287                 #               del self.tickets[host]
288                 #               logger.info("RT: %s no longer down." % host)
289
290         # Update Tickets
291         def updateTickets(self):
292                 logger.info("Refreshing DB.")
293                 for host in self.tickets.keys():
294                         # Put back in Q to refresh
295                         self.q_toRT.put(host)
296
297         def cleanTickets(self):
298                 while 1:
299                         self.remTickets()
300                         self.updateTickets()
301                         time.sleep(RTSLEEP)
302         
303 def main():
304         logger.setLevel(logging.DEBUG)
305         ch = logging.StreamHandler()
306         ch.setLevel(logging.DEBUG)
307         formatter = logging.Formatter('%(message)s')
308         ch.setFormatter(formatter)
309         logger.addHandler(ch)
310
311         tickets = rt_tickets()
312         database.dbDump("ad_dbTickets", tickets)
313
314
315 if __name__ == '__main__':
316     main()