merge from 2.0 branch
[monitor.git] / nodequery.py
1 #!/usr/bin/python
2
3
4 import sys
5 from monitor import database
6 from monitor.common import *
7 from monitor.model import Record
8 import glob
9 import os
10 import traceback
11
12 import time
13 import re
14 import string
15
16 from monitor.wrapper import plc, plccache
17 api = plc.getAuthAPI()
18
19 from monitor.database.info.model import FindbadNodeRecord, FindbadPCURecord, session
20 from monitor import util
21 from monitor import config
22
23
24 class NoKeyException(Exception): pass
25
26 def daysdown_print_nodeinfo(fbnode, hostname):
27         fbnode['hostname'] = hostname
28         fbnode['daysdown'] = Record.getStrDaysDown(fbnode)
29         fbnode['intdaysdown'] = Record.getDaysDown(fbnode)
30
31         print "%(intdaysdown)5s %(hostname)-44s | %(state)10.10s | %(daysdown)s" % fbnode
32
33 def fb_print_nodeinfo(fbnode, hostname, fields=None):
34         #fbnode['hostname'] = hostname
35         #fbnode['checked'] = diff_time(fbnode['checked'])
36         if fbnode['bootcd_version']:
37                 fbnode['bootcd_version'] = fbnode['bootcd_version'].split()[-1]
38         else:
39                 fbnode['bootcd_version'] = "unknown"
40         fbnode['pcu'] = color_pcu_state(fbnode)
41
42         if not fields:
43                 if ( fbnode['observed_status'] is not None and \
44                    'DOWN' in fbnode['observed_status'] ) or \
45                    fbnode['kernel_version'] is None:
46                         fbnode['kernel_version'] = ""
47                 else:
48                         fbnode['kernel_version'] = fbnode['kernel_version'].split()[2]
49
50                 if fbnode['plc_node_stats'] is not None:
51                         fbnode['boot_state'] = fbnode['plc_node_stats']['boot_state']
52                 else:
53                         fbnode['boot_state'] = "unknown"
54
55                 try:
56                         if len(fbnode['nodegroups']) > 0:
57                                 fbnode['category'] = fbnode['nodegroups'][0]
58                 except:
59                         #print "ERROR!!!!!!!!!!!!!!!!!!!!!"
60                         pass
61
62                 print "%(hostname)-45s | %(date_checked)11.11s | %(boot_state)5.5s| %(observed_status)8.8s | %(ssh_status)5.5s | %(pcu)6.6s | %(bootcd_version)6.6s | %(kernel_version)s" % fbnode
63         else:
64                 format = ""
65                 for f in fields:
66                         format += "%%(%s)s " % f
67                 print format % fbnode
68
69 def first(path):
70         indexes = path.split(".")
71         return indexes[0]
72         
73 def get(fb, path):
74     indexes = path.split(".")
75     values = fb
76     for index in indexes:
77                 if values and index in values:
78                         values = values[index]
79                 else:
80                         raise NoKeyException(index)
81     return values
82
83 def verifyType(constraints, data):
84         """
85                 constraints is a list of key, value pairs.
86                 # [ {... : ...}==AND , ... , ... , ] == OR
87         """
88         con_or_true = False
89         for con in constraints:
90                 #print "con: %s" % con
91                 if len(con.keys()) == 0:
92                         con_and_true = False
93                 else:
94                         con_and_true = True
95
96                 for key in con.keys():
97                         #print "looking at key: %s" % key
98                         if data is None:
99                                 con_and_true = False
100                                 break
101
102                         try:
103                                 get(data,key)
104                                 o = con[key]
105                                 if o.name() == "Match":
106                                         if get(data,key) is not None:
107                                                 value_re = re.compile(o.value)
108                                                 con_and_true = con_and_true & (value_re.search(get(data,key)) is not None)
109                                         else:
110                                                 con_and_true = False
111                                 elif o.name() == "ListMatch":
112                                         if get(data,key) is not None:
113                                                 match = False
114                                                 for listitem in get(data,key):
115                                                         value_re = re.compile(o.value)
116                                                         if value_re.search(listitem) is not None:
117                                                                 match = True
118                                                                 break
119                                                 con_and_true = con_and_true & match
120                                         else:
121                                                 con_and_true = False
122                                 elif o.name() == "Is":
123                                         con_and_true = con_and_true & (get(data,key) == o.value)
124                                 elif o.name() == "FilledIn":
125                                         con_and_true = con_and_true & (len(get(data,key)) > 0)
126                                 elif o.name() == "PortOpen":
127                                         if get(data,key) is not None:
128                                                 v = get(data,key)
129                                                 con_and_true = con_and_true & (v[str(o.value)] == "open")
130                                         else:
131                                                 con_and_true = False
132                                 else:
133                                         value_re = re.compile(o.value)
134                                         con_and_true = con_and_true & (value_re.search(get(data,key)) is not None)
135
136                         except NoKeyException, key:
137                                 print "missing key %s" % key,
138                                 pass
139                                 #print "missing key %s" % key
140                                 #con_and_true = False
141
142                 con_or_true = con_or_true | con_and_true
143
144         return con_or_true
145
146 def verifyDBrecord(constraints, record):
147         """
148                 constraints is a list of key, value pairs.
149                 # [ {... : ...}==AND , ... , ... , ] == OR
150         """
151         def has_key(obj, key):
152                 try:
153                         x = obj.__getattribute__(key)
154                         return True
155                 except:
156                         return False
157
158         def get_val(obj, key):
159                 try:
160                         return obj.__getattribute__(key)
161                 except:
162                         return None
163
164         def get(obj, path):
165                 indexes = path.split("/")
166                 value = get_val(obj,indexes[0])
167                 if value is not None and len(indexes) > 1:
168                         for key in indexes[1:]:
169                                 if key in value:
170                                         value = value[key]
171                                 else:
172                                         raise NoKeyException(key)
173                 return value
174
175         #print constraints, record
176
177         con_or_true = False
178         for con in constraints:
179                 #print "con: %s" % con
180                 if len(con.keys()) == 0:
181                         con_and_true = False
182                 else:
183                         con_and_true = True
184
185                 for key in con.keys():
186                         #print "looking at key: %s" % key
187                         if has_key(record, key):
188                                 value_re = re.compile(con[key])
189                                 if type([]) == type(get(record,key)):
190                                         local_or_true = False
191                                         for val in get(record,key):
192                                                 local_or_true = local_or_true | (value_re.search(val) is not None)
193                                         con_and_true = con_and_true & local_or_true
194                                 else:
195                                         if get(record,key) is not None:
196                                                 con_and_true = con_and_true & (value_re.search(get(record,key)) is not None)
197                         else:
198                                 print "missing key %s" % key,
199                                 pass
200
201                 con_or_true = con_or_true | con_and_true
202
203         return con_or_true
204
205 def verify(constraints, data):
206         """
207                 constraints is a list of key, value pairs.
208                 # [ {... : ...}==AND , ... , ... , ] == OR
209         """
210         con_or_true = False
211         for con in constraints:
212                 #print "con: %s" % con
213                 if len(con.keys()) == 0:
214                         con_and_true = False
215                 else:
216                         con_and_true = True
217
218                 for key in con.keys():
219                         #print "looking at key: %s" % key
220                         if first(key) in data: 
221                                 value_re = re.compile(con[key])
222                                 if type([]) == type(get(data,key)):
223                                         local_or_true = False
224                                         for val in get(data,key):
225                                                 local_or_true = local_or_true | (value_re.search(val) is not None)
226                                         con_and_true = con_and_true & local_or_true
227                                 else:
228                                         if get(data,key) is not None:
229                                                 con_and_true = con_and_true & (value_re.search(get(data,key)) is not None)
230                         elif first(key) not in data:
231                                 print "missing key %s" % first(key)
232
233                 con_or_true = con_or_true | con_and_true
234
235         return con_or_true
236
237 def query_to_dict(query):
238         
239         ad = []
240
241         or_queries = query.split('||')
242         for or_query in or_queries:
243                 and_queries = or_query.split('&&')
244
245                 d = {}
246
247                 for and_query in and_queries:
248                         (key, value) = and_query.split('=')
249                         d[key] = value
250
251                 ad.append(d)
252         
253         return ad
254
255 def pcu_in(fbdata):
256         #if 'plcnode' in fbdata:
257         if 'plc_node_stats' in fbdata:
258                 if fbdata['plc_node_stats'] and 'pcu_ids' in fbdata['plc_node_stats']:
259                         if len(fbdata['plc_node_stats']['pcu_ids']) > 0:
260                                 return True
261         return False
262
263 def pcu_select(str_query, nodelist=None):
264         pcunames = []
265         nodenames = []
266         if str_query is None: return (nodenames, pcunames)
267
268         if True:
269                 fbquery = FindbadNodeRecord.get_all_latest()
270                 fb_nodelist = [ n.hostname for n in fbquery ]
271         if True:
272                 # NOTE: this doesn't work when there are only a few records current.
273                 # pcu_select should apply to all pcus globally, not just the most recent records.
274                 fbpcuquery = FindbadPCURecord.get_all_latest()
275                 fbpcu_list = [ p.plc_pcuid for p in fbpcuquery ]
276
277         dict_query = query_to_dict(str_query)
278         print "dict_query", dict_query
279         print 'length %s' % len(fbpcuquery.all())
280
281         for pcurec in fbpcuquery:
282                 pcuinfo = pcurec.to_dict()
283                 if verify(dict_query, pcuinfo):
284                         #nodenames.append(noderec.hostname)
285                         #print 'appending %s' % pcuinfo['plc_pcuid']
286                         pcunames.append(pcuinfo['plc_pcuid'])
287
288         #for noderec in fbquery:
289         #       if nodelist is not None: 
290         #               if noderec.hostname not in nodelist: continue
291 #       
292 #               fb_nodeinfo  = noderec.to_dict()
293 #               if pcu_in(fb_nodeinfo):
294 #                       pcurec = FindbadPCURecord.get_latest_by(plc_pcuid=get(fb_nodeinfo, 
295 #                                                                                                       'plc_node_stats.pcu_ids')[0]).first()
296 #                       if pcurec:
297 #                               pcuinfo = pcurec.to_dict()
298 #                               if verify(dict_query, pcuinfo):
299 #                                       nodenames.append(noderec.hostname)
300 #                                       pcunames.append(pcuinfo['plc_pcuid'])
301         return (nodenames, pcunames)
302
303 def node_select(str_query, nodelist=None, fb=None):
304
305         hostnames = []
306         if str_query is None: return hostnames
307
308         #print str_query
309         dict_query = query_to_dict(str_query)
310         #print dict_query
311
312         for node in nodelist:
313                 #if nodelist is not None: 
314                 #       if node not in nodelist: continue
315
316                 try:
317                         fb_noderec = None
318                         #fb_noderec = FindbadNodeRecord.query.filter(FindbadNodeRecord.hostname==node).order_by(FindbadNodeRecord.date_checked.desc()).first()
319                         fb_noderec = FindbadNodeRecord.get_latest_by(hostname=node)
320                 except:
321                         print traceback.print_exc()
322                         continue
323
324                 if fb_noderec:
325                         fb_nodeinfo = fb_noderec.to_dict()
326
327                         #fb_nodeinfo['pcu'] = color_pcu_state(fb_nodeinfo)
328                         #if 'plcnode' in fb_nodeinfo:
329                         #       fb_nodeinfo.update(fb_nodeinfo['plcnode'])
330
331                         #if verifyDBrecord(dict_query, fb_nodeinfo):
332                         if verify(dict_query, fb_nodeinfo):
333                                 #print fb_nodeinfo.keys()
334                                 #print node #fb_nodeinfo
335                                 hostnames.append(node)
336                         else:
337                                 #print "NO MATCH", node
338                                 pass
339         
340         return hostnames
341
342
343 def main():
344
345         from monitor import parser as parsermodule
346         parser = parsermodule.getParser()
347
348         parser.set_defaults(node=None, fromtime=None, select=None, list=None, listkeys=False,
349                                                 pcuselect=None, nodelist=None, daysdown=None, fields=None)
350         parser.add_option("", "--daysdown", dest="daysdown", action="store_true",
351                                                 help="List the node state and days down...")
352         parser.add_option("", "--select", dest="select", metavar="key=value", 
353                                                 help="List all nodes with the given key=value pattern")
354         parser.add_option("", "--fields", dest="fields", metavar="key,list,...", 
355                                                 help="a list of keys to display for each entry.")
356         parser.add_option("", "--list", dest="list", action="store_true", 
357                                                 help="Write only the hostnames as output.")
358         parser.add_option("", "--pcuselect", dest="pcuselect", metavar="key=value", 
359                                                 help="List all nodes with the given key=value pattern")
360         parser.add_option("", "--nodelist", dest="nodelist", metavar="nodelist.txt", 
361                                                 help="A list of nodes to bring out of debug mode.")
362         parser.add_option("", "--listkeys", dest="listkeys", action="store_true",
363                                                 help="A list of nodes to bring out of debug mode.")
364         parser.add_option("", "--fromtime", dest="fromtime", metavar="YYYY-MM-DD",
365                                         help="Specify a starting date from which to begin the query.")
366
367         parser = parsermodule.getParser(['defaults'], parser)
368         config = parsermodule.parse_args(parser)
369         
370         if config.fromtime:
371                 path = "archive-pdb"
372                 archive = database.SPickle(path)
373                 d = datetime_fromstr(config.fromtime)
374                 glob_str = "%s*.production.findbad.pkl" % d.strftime("%Y-%m-%d")
375                 os.chdir(path)
376                 #print glob_str
377                 file = glob.glob(glob_str)[0]
378                 #print "loading %s" % file
379                 os.chdir("..")
380                 fb = archive.load(file[:-4])
381         else:
382                 #fbnodes = FindbadNodeRecord.select(FindbadNodeRecord.q.hostname, orderBy='date_checked',distinct=True).reversed()
383                 fb = None
384
385         if config.nodelist:
386                 nodelist = util.file.getListFromFile(config.nodelist)
387         else:
388                 # NOTE: list of nodes should come from findbad db.   Otherwise, we
389                 # don't know for sure that there's a record in the db..
390                 plcnodes = plccache.l_nodes
391                 nodelist = [ node['hostname'] for node in plcnodes ]
392                 #nodelist = ['planetlab-1.cs.princeton.edu']
393
394         pculist = None
395         if config.select is not None and config.pcuselect is not None:
396                 nodelist = node_select(config.select, nodelist, fb)
397                 nodelist, pculist = pcu_select(config.pcuselect, nodelist)
398         elif config.select is not None:
399                 nodelist = node_select(config.select, nodelist, fb)
400         elif config.pcuselect is not None:
401                 nodelist, pculist = pcu_select(config.pcuselect, nodelist)
402
403         if pculist:
404                 for pcu in pculist:
405                         print pcu
406
407         for node in nodelist:
408                 config.node = node
409
410                 if node not in nodelist:
411                         continue
412
413                 try:
414                         # Find the most recent record
415                         fb_noderec = FindbadNodeRecord.get_latest_by(hostname=node) 
416                         if not fb_noderec: continue
417                         fb_nodeinfo = fb_noderec.to_dict()
418                 except:
419                         print traceback.print_exc()
420                         continue
421
422                 if config.listkeys:
423                         print "Primary keys available in the findbad object:"
424                         for key in fb_nodeinfo.keys():
425                                 print "\t",key
426                         sys.exit(0)
427                         
428
429                 if config.list:
430                         print node
431                 else:
432                         if config.daysdown:
433                                 daysdown_print_nodeinfo(fb_nodeinfo, node)
434                         else:
435                                 if config.select:
436                                         if config.fields:
437                                                 fields = config.fields.split(",")
438                                         else:
439                                                 fields = None
440
441                                         fb_print_nodeinfo(fb_nodeinfo, node, fields)
442                                 elif not config.select and 'state' in fb_nodeinfo:
443                                         fb_print_nodeinfo(fb_nodeinfo, node)
444                                 else:
445                                         pass
446                 
447 if __name__ == "__main__":
448         main()