commit of tools I use, but are not documented or guaranteed to work for anyone
[monitor.git] / unified_model.py
1 #!/usr/bin/python
2
3 import soltesz
4
5 import plc
6 import auth
7 api = plc.PLC(auth.auth, auth.plc)
8
9 import config
10 import mailer
11 import time
12
13 def gethostlist(hostlist_file):
14         return config.getListFromFile(hostlist_file)
15         
16         #nodes = api.GetNodes({'peer_id' : None}, ['hostname'])
17         #return [ n['hostname'] for n in nodes ]
18
19 def array_to_priority_map(array):
20         """ Create a mapping where each entry of array is given a priority equal
21         to its position in the array.  This is useful for subsequent use in the
22         cmpMap() function."""
23         map = {}
24         count = 0
25         for i in array:
26                 map[i] = count
27                 count += 1
28         return map
29
30 def cmpValMap(v1, v2, map):
31         if v1 in map and v2 in map and map[v1] < map[v2]:
32                 return 1
33         elif v1 in map and v2 in map and map[v1] > map[v2]:
34                 return -1
35         elif v1 in map and v2 in map:
36                 return 0
37         else:
38                 raise Exception("No index %s or %s in map" % (v1, v2))
39
40 def cmpCategoryVal(v1, v2):
41         map = array_to_priority_map([ None, 'ALPHA', 'PROD', 'OLDBOOTCD', 'UNKNOWN', 'FORCED', 'ERROR', ])
42         return cmpValMap(v1,v2,map)
43
44
45 class PCU:
46         def __init__(self, hostname):
47                 self.hostname = hostname
48
49         def reboot(self):
50                 return True
51         def available(self):
52                 return True
53         def previous_attempt(self):
54                 return True
55         def setValidMapping(self):
56                 pass
57
58 class Penalty:
59         def __init__(self, key, valuepattern, action):
60                 pass
61
62 class PenaltyMap:
63         def __init__(self):
64                 pass
65
66         # connect one penalty to another, in a FSM diagram.  After one
67         #       condition/penalty is applied, move to the next phase.
68
69
70 fb = soltesz.dbLoad("findbad")
71
72 class RT(object):
73         def __init__(self, ticket_id = None):
74                 self.ticket_id = ticket_id
75                 if self.ticket_id:
76                         print "getting ticket status",
77                         self.status = mailer.getTicketStatus(self.ticket_id)
78                         print self.status
79
80         def setTicketStatus(self, status):
81                 mailer.setTicketStatus(self.ticket_id, status)
82                 self.status = mailer.getTicketStatus(self.ticket_id)
83                 return True
84         
85         def getTicketStatus(self):
86                 if not self.status:
87                         self.status = mailer.getTicketStatus(self.ticket_id)
88                 return self.status
89
90         def closeTicket(self):
91                 mailer.closeTicketViaRT(self.ticket_id) 
92
93         def email(self, subject, body, to):
94                 self.ticket_id = mailer.emailViaRT(subject, body, to, self.ticket_id)
95                 return self.ticket_id
96
97 class Message(object):
98         def __init__(self, subject, message, via_rt=True, ticket_id=None, **kwargs):
99                 self.via_rt = via_rt
100                 self.subject = subject
101                 self.message = message
102                 self.rt = RT(ticket_id)
103
104         def send(self, to):
105                 if self.via_rt:
106                         return self.rt.email(self.subject, self.message, to)
107                 else:
108                         return mailer.email(self.subject, self.message, to)
109
110 class Recent(object):
111         def __init__(self, withintime):
112                 self.withintime = withintime
113                 self.time = time.time()
114                 self.action_taken = False
115
116         def isRecent(self):
117                 if self.time + self.withintime < time.time():
118                         self.action_taken = False
119
120                 if self.time + self.withintime > time.time() and self.action_taken:
121                         return True
122                 else:
123                         return False
124
125         def unsetRecent(self):
126                 self.action_taken = False
127                 self.time = time.time()
128                 return True
129
130         def setRecent(self):
131                 self.action_taken = True
132                 self.time = time.time()
133                 return True
134                 
135 class PersistFlags(Recent):
136         def __new__(typ, id, *args, **kwargs):
137                 if 'db' in kwargs:
138                         db = kwargs['db']
139                         del kwargs['db']
140                 else:
141                         db = "persistflags"
142
143                 try:
144                         pm = soltesz.dbLoad(db)
145                 except:
146                         soltesz.dbDump(db, {})
147                         pm = soltesz.dbLoad(db)
148                 #print pm
149                 if id in pm:
150                         obj = pm[id]
151                 else:
152                         obj = super(PersistFlags, typ).__new__(typ, *args, **kwargs)
153                         for key in kwargs.keys():
154                                 obj.__setattr__(key, kwargs[key])
155
156                 obj.db = db
157                 return obj
158
159         def __init__(self, id, withintime, **kwargs):
160                 self.id = id
161                 Recent.__init__(self, withintime)
162
163         def save(self):
164                 pm = soltesz.dbLoad(self.db)
165                 pm[self.id] = self
166                 soltesz.dbDump(self.db, pm)
167
168         def resetFlag(self, name):
169                 self.__setattr__(name, False)
170
171         def setFlag(self, name):
172                 self.__setattr__(name, True)
173                 
174         def getFlag(self, name):
175                 try:
176                         return self.__getattribute__(name)
177                 except:
178                         self.__setattr__(name, False)
179                         return False
180
181         def setRecentFlag(self, name):
182                 self.setFlag(name)
183                 self.setRecent()
184
185         def getRecentFlag(self, name):
186                 # if recent and flag set -> true
187                 # else false
188                 try:
189                         return self.isRecent() & self.__getattribute__(name)
190                 except:
191                         self.__setattr__(name, False)
192                         return False
193
194 class PersistMessage(Message):
195         def __new__(typ, id, subject, message, via_rt, **kwargs):
196                 if 'db' in kwargs:
197                         db = kwargs['db']
198                 else:
199                         db = "persistmessages"
200
201                 try:
202                         pm = soltesz.dbLoad(db)
203                 except:
204                         soltesz.dbDump(db, {})
205                         pm = soltesz.dbLoad(db)
206
207                 #print pm
208                 if id in pm:
209                         print "Using existing object"
210                         obj = pm[id]
211                 else:
212                         print "creating new object"
213                         obj = super(PersistMessage, typ).__new__(typ, [id, subject, message, via_rt], **kwargs)
214                         obj.id = id
215                         obj.actiontracker = Recent(3*60*60*24)
216                         obj.ticket_id = None
217
218                 obj.db = db
219                 return obj
220
221         def __init__(self, id, subject, message, via_rt=True, **kwargs):
222                 print "initializing object: %s" % self.ticket_id
223                 self.id = id
224                 Message.__init__(self, subject, message, via_rt, self.ticket_id)
225
226         def reset(self):
227                 self.actiontracker.unsetRecent()
228
229         def send(self, to):
230                 if not self.actiontracker.isRecent():
231                         self.ticket_id = Message.send(self, to)
232                         self.actiontracker.setRecent()
233
234                         #print "recording object for persistance"
235                         pm = soltesz.dbLoad(self.db)
236                         pm[self.id] = self
237                         soltesz.dbDump(self.db, pm)
238                 else:
239                         # NOTE: only send a new message every week, regardless.
240                         print "Not sending to host b/c not within window of 6 days"
241                         pass
242
243 class MonitorMessage(object):
244         def __new__(typ, id, *args, **kwargs):
245                 if 'db' in kwargs:
246                         db = kwargs['db']
247                 else:
248                         db = "monitormessages"
249
250                 try:
251                         if 'reset' in kwargs and kwargs['reset'] == True:
252                                 soltesz.dbDump(db, {})
253                         pm = soltesz.dbLoad(db)
254                 except:
255                         soltesz.dbDump(db, {})
256                         pm = soltesz.dbLoad(db)
257
258                 #print pm
259                 if id in pm:
260                         print "Using existing object"
261                         obj = pm[id]
262                 else:
263                         print "creating new object"
264                         obj = super(object, typ).__new__(typ, id, *args, **kwargs)
265                         obj.id = id
266                         obj.sp = PersistSitePenalty(id, 0)
267
268                 obj.db = db
269                 return obj
270
271         def __init__(self, id, message):
272                 pass
273                 
274
275 class SitePenalty(object):
276         penalty_map = [] 
277         penalty_map.append( { 'name': 'noop',                   'enable'   : lambda host: None,
278                                                                                                         'disable'  : lambda host: None } )
279         penalty_map.append( { 'name': 'nocreate',               'enable'   : lambda host: plc.removeSliceCreation(host),
280                                                                                                         'disable'  : lambda host: plc.enableSliceCreation(host) } )
281         penalty_map.append( { 'name': 'suspendslices',  'enable'   : lambda host: plc.suspendSlices(host),
282                                                                                                         'disable'  : lambda host: plc.enableSlices(host) } )
283
284         #def __init__(self, index=0, **kwargs):
285         #       self.index = index
286
287         def get_penalties(self):
288                 # TODO: get penalties actually applied to a node from PLC DB.
289                 return [ n['name'] for n in SitePenalty.penalty_map ] 
290
291         def increase(self):
292                 self.index = self.index + 1
293                 if self.index > len(SitePenalty.penalty_map)-1: self.index = len(SitePenalty.penalty_map)-1
294                 return True
295
296         def decrease(self):
297                 self.index = self.index - 1
298                 if self.index < 0: self.index = 0
299                 return True
300
301         def apply(self, host):
302
303                 for i in range(len(SitePenalty.penalty_map)-1,self.index,-1):
304                         print "\tdisabling %s on %s" % (SitePenalty.penalty_map[i]['name'], host)
305                         SitePenalty.penalty_map[i]['disable'](host)
306
307                 for i in range(0,self.index+1):
308                         print "\tapplying %s on %s" % (SitePenalty.penalty_map[i]['name'], host)
309                         SitePenalty.penalty_map[i]['enable'](host)
310
311                 return
312
313
314
315 class PersistSitePenalty(SitePenalty):
316         def __new__(typ, id, index, **kwargs):
317                 if 'db' in kwargs:
318                         db = kwargs['db']
319                 else:
320                         db = "persistpenalties"
321
322                 try:
323                         if 'reset' in kwargs and kwargs['reset'] == True:
324                                 soltesz.dbDump(db, {})
325                         pm = soltesz.dbLoad(db)
326                 except:
327                         soltesz.dbDump(db, {})
328                         pm = soltesz.dbLoad(db)
329
330                 #print pm
331                 if id in pm:
332                         print "Using existing object"
333                         obj = pm[id]
334                 else:
335                         print "creating new object"
336                         obj = super(PersistSitePenalty, typ).__new__(typ, [index], **kwargs)
337                         obj.id = id
338                         obj.index = index
339
340                 obj.db = db
341                 return obj
342
343         def __init__(self, id, index, **kwargs):
344                 self.id = id
345                 #SitePenalty.__init__(self, self.index)
346
347         def save(self):
348                 pm = soltesz.dbLoad(self.db)
349                 pm[self.id] = self
350                 soltesz.dbDump(self.db, pm)
351
352
353
354 class Target:
355         """
356                 Each host has a target set of attributes.  Some may be set manually,
357                 or others are set globally for the preferred target.
358
359                 For instance:
360                         All nodes in the Alpha or Beta group would have constraints like:
361                                 [ { 'state' : 'BOOT', 'kernel' : '2.6.22' } ]
362         """
363         def __init__(self, constraints):
364                 self.constraints = constraints
365
366         def verify(self, data):
367                 """
368                         self.constraints is a list of key, value pairs.
369                         # [ {... : ...}==AND , ... , ... , ] == OR
370                 """
371                 con_or_true = False
372                 for con in self.constraints:
373                         #print "con: %s" % con
374                         con_and_true = True
375                         for key in con.keys():
376                                 #print "looking at key: %s" % key
377                                 if key in data: 
378                                         #print "%s %s" % (con[key], data[key])
379                                         con_and_true = con_and_true & (con[key] in data[key])
380                                 elif key not in data:
381                                         print "missing key %s" % key
382                                         con_and_true = False
383
384                         con_or_true = con_or_true | con_and_true
385
386                 return con_or_true
387
388 class NodeRecord:
389         def __init__(self, hostname, target):
390                 self.hostname = hostname
391                 self.pcu = PCU(hostname)
392                 self.ticket = None
393                 self.target = target
394                 if hostname in fb['nodes']:
395                         self.data = fb['nodes'][hostname]['values']
396                 else:
397                         raise Exception("Hostname not in scan database")
398
399         def get(self):
400                 pass
401         def severity(self):
402                 category = self.data['category']
403                 prev_category = self.data['prev_category']
404                 val = cmpCategoryVal(category, prev_category)
405                 return val 
406         def open_tickets(self):
407                 if self.ticket and self.ticket.status['status'] == 'open':
408                         return 1
409                 return 0
410         def setIntrospect(self):
411                 pass
412
413         def email_notice(self):
414                 message = self._get_message_for_condition()
415                 message.send(self._get_contacts_for_condition())
416                 return True
417         def close_ticket(self):
418                 if self.ticket:
419                         self.ticket.closeTicket()
420
421         def exempt_from_penalties(self):
422                 bl = soltesz.dbLoad("l_blacklist")
423                 return self.hostname in bl
424
425         def penalties(self):
426                 return []
427         def escellate_penalty(self):
428                 return True
429         def reduce_penalty(self):
430                 return True
431
432
433         def atTarget(self):
434                 return self.target.verify(self.data)
435
436         def _get_condition(self):
437                 return self.data['category'].lower()
438
439         def _get_stage(self):
440                 "improvement"
441                 "firstnotice_noop"
442                 "secondnotice_noslicecreation"
443                 "thirdnotice_disableslices"
444
445                 delta = current_time - self.data['time']
446
447         def _get_message_for_condition(self):
448                 pass
449         def _get_contacts_for_condition(self):
450                 pass
451
452 if __name__ == "__main__":
453         #r = RT()
454         #r.email("test", "body of test message", ['soltesz@cs.princeton.edu'])
455         from emailTxt import mailtxt
456         soltesz.dbDump("persistmessages", {});
457         args = {'url_list': 'http://www.planet-lab.org/bootcds/planet1.usb\n','hostname': 'planet1','hostname_list': ' blahblah -  days down\n'}
458         m = PersistMessage("blue", "test 1", mailtxt.newdown_one[1] % args, True)
459         m.send(['soltesz@cs.utk.edu'])
460         m = PersistMessage("blue", "test 1 - part 2", mailtxt.newalphacd_one[1] % args, True)
461         # TRICK timer to thinking some time has passed.
462         m.actiontracker.time = time.time() - 6*60*60*24
463         m.send(['soltesz@cs.utk.edu'])