Initial Checkin.
[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 comon
10 from threading import *
11
12 # RT database access constants file
13 RT_DB_CONSTANTS_PATH='/etc/planetlab/rt_db'
14
15 #Logging
16 logger = logging.getLogger("monitor")
17
18 # seconds between ticket update
19 RTSLEEP = 7200
20
21 def stripQuotes( str ):
22         quotes= ["'",'"']
23         if str[0] in quotes:
24                 str= str[1:]
25         if str[len(str)-1] in quotes:
26                 str= str[:len(str)-1]
27         return str
28
29
30 def readConstantsFile( file_path ):
31         """
32         read a file consisting of lines of
33         NAME=VALUE
34         NAME='VALUE'
35         NAME="VALUE"
36         and return a dictionary of the values.
37
38         blank lines, and lines starting with # (comments) are skipped
39         """
40
41         contents= {}
42
43         try:
44                 input_file= file(file_path,"r")
45         except IOError, err:
46                 return None
47
48         for line in input_file:
49                 if line[0] == "#":
50                         continue
51                 line= string.strip(line)
52                 if len(line) == 0:
53                         continue
54
55                 parts= string.split(line,"=",)
56                 if len(parts) <> 2:
57                         continue
58
59                 contents[parts[0]]= stripQuotes(parts[1])
60
61         return contents
62
63
64
65 def open_rt_db():
66
67         # read plc database passwords and connect
68         rt_db_constants= readConstantsFile(RT_DB_CONSTANTS_PATH)
69         if rt_db_constants is None:
70                 print "Unable to read database access constants from %s" % \
71                           RT_DB_CONSTANTS_PATH
72                 return -1
73
74         try:
75                 rt_db = MySQLdb.connect(host=rt_db_constants['RT_DB_HOST'],
76                                 user=rt_db_constants['RT_DB_USER'],
77                                 passwd=rt_db_constants['RT_DB_PASSWORD'],
78                                 db=rt_db_constants['RT_DB_NAME'])
79         except Error:
80                 print "Failed to connect to RT database"
81                 return -1
82
83         return rt_db
84
85
86
87
88 def rt_tickets(hostname):
89         db = open_rt_db()
90         sql = """SELECT distinct Tk.id, Tk.Status, Tk.Subject
91                          FROM Tickets AS Tk
92                          JOIN Transactions AS Tr ON Tk.id=Tr.ObjectId
93                          JOIN Attachments AS At ON Tr.id=At.TransactionID
94                          WHERE (At.Content LIKE '%%%s%%' OR
95                                 At.Subject LIKE '%%%s%%') AND
96                                 (Tk.Status = 'new' OR Tk.Status = 'open') AND
97                                 Tk.Queue = 3
98                          ORDER BY Tk.Status, Tk.LastUpdated DESC""" \
99                          % (hostname,hostname)
100
101         try:
102                 # create a 'cursor' (required by MySQLdb)
103                 c = db.cursor()
104                 c.execute(sql)
105         except Exception, err:
106                 print "Could not execute RT query %s" %err
107                 return -1
108
109         # fetch all rows (list of lists)
110         raw = c.fetchall()
111
112         # map list of lists (raw) to list of dicts (tickets) 
113         # when int gets pulls from SQL into python ints are converted to LONG to
114         # prevent overflow .. convert back
115         tickets = map(lambda x: {"ticket_id":int(x[0]),
116                                 "status":x[1],
117                                 "subj":x[2]},
118                                 raw)
119         db.close()
120
121         return tickets
122
123
124 '''
125 Finds tickets associated with hostnames.
126 The idea is if you give it an array of host names,
127 presumeably from comon's list of bad nodes, it starts
128 a few threads to query RT.  RT takes a while to return.
129
130 This is turning into a reinvention of DB design, which I dont believe in.
131 In an effort to keep things minimal, here's the basic algo:
132
133 Give list of hostnames to RT()
134 Finds tickets associate with new hostnames (not in dict(tickets)).
135 Remove nodes that have come backup. Don't care of ticket is closed after first query.
136 Another thread refresh tickets of nodes already in dict and remove nodes that have come up. 
137 '''
138 class RT(Thread):
139         def __init__(self, tickets, bucket, target = None): 
140                 # Time of last update of ticket DB
141                 self.lastupdated = 0
142                 # Queue() is MP/MC self locking 
143                 self.bucket = bucket 
144                 #DB of tickets.  Name -> ticket
145                 self.tickets = tickets
146                 Thread.__init__(self,target = self.getTickets)
147
148         # Takes node from alldownq, gets tickets.  
149         # Thread that actually gets the tickets.
150         def getTickets(self):
151                 while 1:
152                         host = self.bucket.get(block = True)
153                         if host == "None": break
154                         #if self.tickets.has_key(host) == False:
155                         logger.debug("Popping from q - %s" %host)
156                         tmp = rt_tickets(host)
157                         if tmp:
158                                 logger.debug("Found tickets for %s" %host)
159                                 self.tickets[host] = tmp 
160
161         # Removes hosts that are no longer down.
162         def remTickets(self):
163                 logger.debug("Removing stale entries from DB.") 
164                 prevdown = self.tickets.keys()
165
166                 currdown = []
167                 #BEGIN HACK.  This should be outside of this file. passed to class.
168                 cmn = comon.Comon(None, None)
169                 cmn.updatebkts()
170                 for bucket in cmn.comonbkts.keys():
171                         for host in getattr(cmn,bucket):
172                                 if host not in currdown: currdown.append(host)
173                 #END HACK
174
175                 # Actually do the comparison
176                 for host in prevdown:
177                         if host not in currdown:
178                                 del self.tickets[host]
179                                 logger.info("%s no longer down" % host)
180
181         # Update Tickets
182         def updateTickets(self):
183                 logger.info("Refreshing DB.")
184                 for host in self.tickets.keys():
185                         # Put back in Q to refresh
186                         self.bucket.put(host)
187
188         def cleanTickets(self):
189                 while 1:
190                         self.remTickets()
191                         self.updateTickets()
192                         time.sleep(RTSLEEP)
193         
194 def main():
195         logger.setLevel(logging.DEBUG)
196         ch = logging.StreamHandler()
197         ch.setLevel(logging.DEBUG)
198         formatter = logging.Formatter('%(message)s')
199         ch.setFormatter(formatter)
200         logger.addHandler(ch)
201
202         bucket = Queue.Queue() 
203         tickets = {}
204         a = RT(tickets, bucket)
205         b = RT(tickets, bucket)
206         c = RT(tickets, bucket)
207         d = RT(tickets, bucket)
208         e = RT(tickets, bucket)
209         a.start()
210         b.start()
211         c.start()
212         d.start()
213         tmp = ('planetlab-2.vuse.vanderbilt.edu', 'planetlab-11.cs.princeton.edu', 'planet03.csc.ncsu.edu', 'planetlab1.pop-rj.rnp.br', 'planet1.halifax.canet4.nodes.planet-lab.org', 'planet1.cavite.nodes.planet-lab.org', 'ds-pl3.technion.ac.il', 'planetlab2.cs.purdue.edu', 'planetlab3.millennium.berkeley.edu', 'planetlab1.unl.edu', 'planetlab1.cs.colorado.edu', 'planetlab02.cs.washington.edu', 'orbpl2.rutgers.edu', 'planetlab2.informatik.uni-erlangen.de', 'pl2.ernet.in', 'neu2.6planetlab.edu.cn', 'planetlab-2.cs.uni-paderborn.de', 'planetlab1.elet.polimi.it', 'planetlab2.iiitb.ac.in', 'server1.planetlab.iit-tech.net', 'planetlab2.iitb.ac.in', 'planetlab1.ece.ucdavis.edu', 'planetlab02.dis.unina.it', 'planetlab-1.dis.uniroma1.it', 'planetlab1.iitb.ac.in', 'pku1.6planetlab.edu.cn', 'planetlab1.warsaw.rd.tp.pl', 'planetlab2.cs.unc.edu', 'csu2.6planetlab.edu.cn', 'pl1.ernet.in', 'planetlab2.georgetown.edu', 'planetlab1.cs.uchicago.edu') 
214         for host in tmp:
215                 bucket.put(host)
216         #et = Thread(target=e.pushHosts)        
217         #et.start()
218         time.sleep(15)
219         print tickets.keys()
220         time.sleep(15)
221         print tickets.keys()
222         time.sleep(15)
223         print tickets.keys()
224         #cmn = comon.Comon(cdb, bucket)
225         #cmn.updatebkts()
226         #for bucket in cmn.comonbkts.keys():
227 #               for host in getattr(cmn,bucket):
228 #                       alldown.put(host)
229 #
230         at = Thread(target=a.cleanTickets)
231         at.start()
232         time.sleep(15)
233         print tickets.keys()
234         os._exit(0)
235
236 if __name__ == '__main__':
237     main()