import repository from arizona
[raven.git] / owl / server / index.py
1 #!/usr/bin/env python
2 """
3 Author: John H. Hartman <jhh@cs.arizona.edu>
4 Date: 2010-3-17
5
6 This is the mod_python index file for Owl.
7
8 This borrows heavily from the index file written by Justin Samuel for the Raven Demo.
9
10 Owl is organized as follows. The data are stored in a MySQL database. There is one MySQL
11 database per Owl database. The "nodes" table contains the information collected on the 
12 Owl clients. The field names are of the form "module.field". The "modules" table contains
13 information about each module, including its name, heading, alignment, and version.
14 The "columns" table contains information about each column (field), including the name, 
15 heading and alignment.
16
17
18 This script will require:
19 mysql
20 mod_python
21 python-MySQLdb
22 python-json
23 """
24
25 import sys
26 from optparse import OptionParser
27 import version
28
29 protocol = "1.3"
30
31 config = {}
32 config["userpasswordfile"] = "/root/mysql-owl-pass.txt"
33 config["userpassword"] = None
34 config["rootpasswordfile"] = "/root/mysql-root-pass.txt"
35 config["rootpassword"] = None
36 config["host"] = "localhost"
37 config["user"] = "owl"
38 config["prefix"] = "owl-"
39 config["url"] = "/owl/"
40 config["path"] = "/usr/local/owl"
41
42 # Parse command line arguments
43
44 oparser = OptionParser(version="%prog " + str(version.version))
45 (options,args) = oparser.parse_args()
46
47 from mod_python import apache, psp, util, Session
48 from cgi import escape
49 from urllib import unquote
50 from distutils.version import LooseVersion
51 import json as json_module # avoid name conflict with json function
52 import MySQLdb
53 import ConfigParser
54 import traceback
55 from db import *
56 import time
57 from owllib import *
58 import string
59 import urllib
60 import owlacl
61
62
63
64 request = None
65
66 info(None, "Owl %s %s %s" % (version.flavor, version.version, version.released))
67
68 # Now parse the owl config file
69
70 parser = ConfigParser.SafeConfigParser()
71 if version.flavor == "production":
72     confFile = "/etc/owl-server.conf"
73 else:
74     confFile = "/etc/owl-server-%s.conf" % (version.flavor)
75
76 parser.read(confFile)
77 for item in parser.items("Owl"):
78     (key, value) = item
79     config[key] = value
80
81 owl_dbname_prefix(config["prefix"])
82
83 info(None, "Happy Birthday!")
84 info(None, time.ctime(time.time()))
85
86 # DB connection info 
87 DB.host = config["host"]
88 DB.user = config["user"]
89 DB.name = None
90 DB.passwd = config["userpassword"]
91 if DB.passwd == None:
92     try:
93         file = config["userpasswordfile"]
94         fd = open(file, "r")
95         DB.passwd = fd.readline().rstrip()
96         fd.close()
97     except:
98         error('Unable to read mysql owl password from "%s"' % (file))
99         error('euid = %s, egid = %s' % (os.geteuid(), os.getegid()))
100         raise
101
102 # ACL setup
103
104 try:
105     file = config["rootpasswordfile"]
106     fd = open(file, "r")
107     password = fd.readline().rstrip()
108     fd.close()
109 except:
110     error('Unable to read mysql root password from "%s"' % (file))
111     raise
112
113 owlacl.init(password)
114
115 def _authorized(dbname, principal, right):
116     try:
117         owlacl.check_authorized(dbname, principal, right)
118     except Exception, e:
119         _error(str(e))
120         return False
121     else:
122         return True
123
124 def _error(msg):
125     global request
126
127     debug(request, msg)
128     if request != None:
129         request.write("ERROR " + msg)
130
131
132 """
133 ************************** OUTPUT ROUTINES **************************
134
135 These routines produce the Owl pages. They make use of the PSP pages in templates/.
136
137 """
138
139
140 #
141 # Display the database contents
142 #
143
144 def index(req, db=None, sortby="basic.id", filterby="basic.id", filterval="*", showheader="true", newer='0', f1='0', legend="true", sortorder="inc", auth=''):
145     global config, request
146
147     request = req
148     dbconn = None
149     try:
150         req.content_type = "text/html"
151         debug(req, "index db=%s sortby=%s newer=%s" % (db,sortby,newer))
152         date = time.strftime('%a %b %d %X UTC %Y',time.gmtime())
153         if db == None:
154             # Show available databases
155             dbconn = DB(name='information_schema')
156             cursor = dbconn.execute("SELECT `SCHEMA_NAME` FROM `SCHEMATA` ORDER BY `SCHEMA_NAME`")
157             rows = cursor.fetchall()
158             dbs = []
159             for row in rows:
160                 name = row[0]
161                 debug(req, "name = %s" % name)
162                 prefix = config["prefix"]
163                 if name.startswith(config["prefix"]):
164                     try:
165                         owlacl.check_authorized(name, auth, 'index')
166                     except:
167                         debug(req, "Skipping '%s'" % (name))
168                         continue
169                     dbs.append(name[len(prefix):])
170             tmpl = psp.PSP(req, filename="templates/index.psp")
171             tmpl.run(vars = { 'dbs' : dbs, 'version' : version.version, 
172                               'date' : version.released, "config" : config,
173                               "flavor" : version.flavor, "date" : date})
174             dbconn.close()
175         else:
176             # Display the specified database.
177
178             dbname = owl_dbname(db)
179             if not _authorized(dbname, auth, 'read'):
180                 return
181             req.headers_out.add('Cache-Control', 'max-age=10')
182             query = util.parse_qs(req.parsed_uri[apache.URI_QUERY])
183             # Compute the cut-off date for records. 
184             # If newer starts with '-' it is relative to the current time.
185
186             if (newer[0] == '-'):
187                 now = time.time()
188                 suffix = newer[-1]
189                 if suffix.isdigit() or suffix == 's':
190                     x = int(newer[1:-1])
191                     delta = x
192                 elif suffix == 'h':
193                     x = int(newer[1:-1])
194                     delta = x * 3600
195                 elif suffix == 'd':
196                     x = int(newer[1:-1])
197                     delta = x * 86400
198                 elif suffix == 'w':
199                     x = int(newer[1:-1])
200                     delta = 7 * 86400
201                 elif suffix == 'm':
202                     x = int(newer[1:-1]) 
203                     delta = 28 * 86400
204                 elif suffix == 'y':
205                     x = int(newer[1:-1]) 
206                     delta =365 * 86400
207                 newer = now - delta
208                 debug(req, "now = %s, delta = %s, newer = %s" % (now,delta,newer))
209             else:
210                 newer = int(newer)
211             try:
212                 dbconn = DB(name=dbname)
213             except MySQLdb.Error, e:
214                 req.write("No database \"%s\"" % (db))
215                 return
216             session = Session.Session(req)
217             try:
218                 hide = session[db + '.hide']
219             except:
220                 hide = []
221             tmpl = psp.PSP(req, filename="templates/display.psp")
222             #tmpl.display_code()
223             tmpl.run(vars = {'dbconn' : dbconn, 
224                     'db' : db, 'sortby' : sortby, 'filterby': filterby,
225                     'filterval': filterval, 'showheader': (showheader=="true"), 
226                     'version' : version.version,
227                     'date': version.released, 'newer' : newer, 'f1' : f1, "config": config, 
228                     "flavor" : version.flavor, "request" : req, 'legend':(legend=="true"),
229                     'sortorder': sortorder, "date" : date, 'query' : query, "hide": hide})
230     finally:
231         if dbconn != None:
232             dbconn.close()
233
234 def purge(req, verbose=0):
235     global config, request
236
237     request = req
238     dbconn = None
239
240     try:
241         req.content_type = "text/html"
242         date = time.strftime('%a %b %d %X UTC %Y',time.gmtime())
243
244         # Show available databases
245         dbconn = DB(name='information_schema')
246         cursor = dbconn.execute("SELECT `SCHEMA_NAME` FROM `SCHEMATA` ORDER BY `SCHEMA_NAME`")
247         rows = cursor.fetchall()
248         dbs = []
249         for row in rows:
250             name = row[0]
251             debug(req, "name = %s" % name)
252             prefix = config["prefix"]
253             if name.startswith(config["prefix"]):
254                 dbs.append({"name": name})
255         dbconn.close()
256         dbconn = None
257
258         for db in dbs:
259             dbconn = DB(name=db["name"])
260
261             db["age"] = 0
262             db["rows"] = 0
263             db["columns"] = []
264
265             cursor = dbconn.execute("SELECT COUNT(*) FROM `nodes`")
266             row = cursor.fetchone()
267             if row:
268                 db["rows"] = int(row[0])
269
270             cursor = dbconn.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'nodes'")
271             db["columns"] = [ row[0] for row in cursor.fetchall() ]
272
273             # if there are no rows, then there's no timestamps
274             if db["rows"] <= 0:
275                 continue
276
277             # if there's no timestamp column, then there's no timestamps
278             if not ("_internal.timestamp" in db["columns"]):
279                 continue
280
281             cursor = dbconn.execute("SELECT MAX(`_internal.timestamp`) FROM `nodes`")
282             row = cursor.fetchone()
283             if row:
284                 db["age"] = int(time.time() - int(row[0]))
285
286             dbconn.close()
287             dbconn = None
288
289         for db in dbs:
290             db["delete"] = False
291             if db["rows"] == 0:
292                 # delete things with no data
293                 db["delete"] = True
294             if db["age"]/60/60/24 > 30:
295                 # delete things with data older than 30 days
296                 db["delete"] = True
297
298         if verbose:
299             req.write("<table>")
300             req.write("<tr><th>db</th><th>rows</th><th>most recent age (seconds / days)</th><th>delete?</th></tr>")
301             for db in dbs:
302                 req.write("<tr><td>" + str(db["name"]) + "</td><td>" + str(db.get("rows",0)) + "</td><td>" + str(db["age"]) +" / " + str(db["age"]/60/60/24) + "</td><td>" + str(db["delete"]) + "</td></tr>")
303             req.write("</table>")
304
305         for db in dbs:
306             if db["delete"]:
307                 _delete_db(db["name"])
308
309         req.write("OK")
310
311     finally:
312         if dbconn != None:
313             dbconn.close()
314
315 def row(req, db=None, id=None, showheader="true", legend="true", auth=''):
316     global config, request
317
318     request = req
319     dbconn = None
320
321     try:
322         req.content_type = "text/html"
323         if db == None:
324             req.write("ERROR: db must be specified")
325             return
326         if id == None:
327             req.write("ERROR: id must be specified")
328             return
329         dbname = owl_dbname(db)
330         if not _authorized(dbname, auth, 'read'):
331             return
332         try:
333             dbconn = DB(name=dbname)
334         except MySQLdb.Error, e:
335             req.write("No database \"%s\"" % (db))
336             return
337         session = Session.Session(req)
338         try:
339             hide = session[db + '.hide']
340         except:
341             hide = []
342         query = util.parse_qs(req.parsed_uri[apache.URI_QUERY])
343         tmpl = psp.PSP(req, filename="templates/row.psp")
344         #tmpl.display_code()
345         tmpl.run(vars = {'dbconn' : dbconn, 
346                 'db' : db, 'showheader': (showheader=="true"), 
347                 'version' : version.version, "flavor" : version.flavor,
348                 "config": config, 'id': id,
349                 "req" : req, 'legend':(legend=="true"), 'hide' : hide,
350                 'query' : query})
351     finally:
352         if dbconn:
353             dbconn.close()
354
355 def prefs(req, db=None, redir=None, auth=''):
356     global request
357
358     request = req
359     dbconn = None
360
361     try:
362         req.content_type = "text/html"
363         if db == None:
364             req.write("ERROR: db must be specified")
365             return
366         dbname = owl_dbname(db)
367         if not _authorized(dbname, auth, 'read'):
368             return
369         try:
370             dbconn = DB(name=dbname)
371         except MySQLdb.Error, e:
372             req.write("No database \"%s\"" % (db))
373             return
374         session = Session.Session(req)
375         try:
376             hide = session[db + '.hide']
377         except:
378             hide = []
379         query = util.parse_qs(req.parsed_uri[apache.URI_QUERY])
380         tmpl = psp.PSP(req, filename="templates/prefs.psp")
381         #tmpl.display_code()
382         tmpl.run(vars = {'dbconn' : dbconn, 
383                 'db' : db, 
384                 'version' : version.version, "flavor" : version.flavor,
385                 "config": config, 
386                 "req" : req, "hide": hide, "query":query, 'redir' : redir})
387     finally:
388         if dbconn:
389             dbconn.close()
390
391
392 def setprefs(req, db=None, auth=''):
393
394     request = req
395     dbconn = None
396
397     debug(req, "setprefs")
398     try:
399         if db == None:
400             req.write("ERROR: db must be specified")
401             return
402         dbname = owl_dbname(db)
403         if not _authorized(dbname, auth, 'read'):
404             return
405         try:
406             dbconn = DB(name=dbname)
407         except:
408             req.write("No database \"%s\"" % (db))
409             return
410         modules = get_modules(dbconn)
411         session = Session.Session(req)
412         session[db + '.hide'] = []
413         keys = req.form.keys()
414         for m in modules:
415             if m.name not in keys:
416                 session[db + '.hide'].append(m.name)
417         session.save()
418         query = util.parse_qs(req.parsed_uri[apache.URI_QUERY])
419         util.redirect(req, req.form.getfirst('redirect') + '?' + 
420                       urllib.urlencode(query, doseq=True))
421     finally:
422         if dbconn:
423             dbconn.close()
424
425
426 def xml(req, db=None, auth=''):
427     global request
428
429     request = req
430     timestamp = time.time()
431     date = time.strftime('%a %b %d %X UTC %Y',time.gmtime(timestamp))
432     dbconn = None
433     try:
434         request = req
435         req.content_type = "text/xml"
436         req.headers_out.add('Cache-Control', 'max-age=10')
437         if db != None:
438             dbname = owl_dbname(db)
439             if not _authorized(dbname, auth, 'read'):
440                 return
441             try:
442                 dbconn=DB(name=dbname)
443             except MySQLdb.Error, e:
444                 req.write("No database \"%s\"" % (db))
445                 return
446             tmpl = psp.PSP(req, filename="templates/xml.psp")
447             tmpl.run(vars = {'db' : db, 'dbconn' : dbconn, "config" : config, 
448                              "flavor":version.flavor, 'timestamp':timestamp,
449                              'date':date})
450             dbconn.close()
451     finally:
452         if dbconn != None:
453             dbconn.close()
454
455
456 def json(req, db=None, what="data", auth=''):
457     global request, config
458
459     request = req
460     dbconn = None
461     try:
462         req.content_type = "text/plain"
463         result = {}
464         if db != None:
465             dbname = owl_dbname(db)
466             if not _authorized(dbname, auth, 'read'):
467                 return
468             try:
469                 dbconn=DB(name=dbname)
470             except MySQLdb.Error, e:
471                 req.write("No database \"%s\"" % (db))
472                 return
473             try:
474                 fields = req.form.getfirst("fields").split(',')
475             except:
476                 fields = ''
477             tmp = []
478             for field in fields:
479                 tmp.append("`%s`" % (field))
480             fieldStr = ','.join(tmp)
481             if fieldStr == '':
482                 fieldStr = '*'
483             dbconn.dict_cursor() # switch to the dictionary cursor
484             if (what == "modules" or what == "all"):
485                 cursor = dbconn.execute("SELECT * FROM `modules` ORDER BY `order`,`module`")
486                 result["modules"] = cursor.fetchall()
487             if (what == "data" or what == "all"):
488                 cursor = dbconn.execute("SELECT %s FROM `nodes` ORDER BY `basic.id`" % (fieldStr))
489                 result["data"] = cursor.fetchall()
490         elif what == "databases":
491             try:
492                 dbconn = DB(name='information_schema')
493             except MySQLdb.Error, e:
494                 req.write("Cannot get databases")
495                 return
496             cursor = dbconn.execute("SELECT `SCHEMA_NAME` FROM `SCHEMATA` ORDER BY `SCHEMA_NAME`")
497             rows = cursor.fetchall()
498             dbs = []
499             for row in rows:
500                 name = row[0]
501                 debug(req, "name = %s" % name)
502                 prefix = config["prefix"]
503                 if name.startswith(config["prefix"]):
504                     dbs.append(name[len(prefix):])
505             result["databases"] = dbs
506         if result != {}:
507             req.write(json_module.write(result))
508     finally:
509         if dbconn != None:
510             dbconn.close()
511
512 """
513 ************************** INPUT ROUTINES **************************
514
515 These routines are used by Owl clients to put data in Owl databases. 
516
517 """
518
519 def _create_db(req, db, key = None, owner = None):
520
521     dbconn = None
522     try:
523         debug(req, "Creating database %s" % (db))
524         dbname = owl_dbname(db)
525         dbconn = DB(name="information_schema")
526         cursor = dbconn.execute("SELECT * FROM `SCHEMATA` WHERE `SCHEMA_NAME` = '%s'" % (dbname))
527         row = cursor.fetchone()
528         if row != None:
529             # The database already exists. 
530             return False
531         try:
532             file = config["rootpasswordfile"]
533             fd = open(file, "r")
534             password = fd.readline().rstrip()
535             fd.close()
536         except:
537             error('Unable to read mysql root password from "%s"' % (file))
538             raise
539         if key is None:
540             key = 'basic.id'
541         dbconn = DB(user="root", passwd=password, name="information_schema")
542         debug(req, "Creating database")
543         dbconn.execute("CREATE DATABASE IF NOT EXISTS `%s`" % (dbname))
544         dbconn.execute("GRANT ALL PRIVILEGES ON `%s` . * TO 'owl'@'%%'" % (dbname))
545         dbconn.execute("GRANT ALL PRIVILEGES ON `%s` . * TO 'owl'@'localhost'" % (dbname))
546
547         dbconn = DB(name=dbname)
548         debug(req, "Creating node table")
549
550         #
551         dbconn.execute("""CREATE TABLE IF NOT EXISTS `nodes`
552            (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY) ENGINE = MYISAM ;""")
553         debug(req, "Creating modules table")
554         dbconn.execute("""CREATE TABLE IF NOT EXISTS `modules`
555            (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
556             `module` VARCHAR(255) NOT NULL,
557             `order` INT NOT NULL,
558             `fields` VARCHAR(1024) NOT NULL,
559             `heading` VARCHAR(255) NOT NULL,
560             `version` VARCHAR(255) NOT NULL) ENGINE = MYISAM ;""")
561         debug(req, "Creating columns table")
562         dbconn.execute("""CREATE TABLE IF NOT EXISTS `columns`
563            (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
564             `field` VARCHAR(255) NOT NULL,
565             `module` VARCHAR(255) NOT NULL,
566             `heading` VARCHAR(255) NOT NULL,
567             `headingAlign` VARCHAR(255) NOT NULL,
568             `align` VARCHAR(255) NOT NULL,
569             `stats` VARCHAR(255) NOT NULL,
570             `type` VARCHAR(255) NOT NULL,
571             `description` VARCHAR(1024)) ENGINE = MYISAM ;""")
572         debug(req, "Creating metadata table")
573         dbconn.execute("""CREATE TABLE IF NOT EXISTS `metadata`
574            (`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
575             `key` VARCHAR(255) NOT NULL,
576             `value` VARCHAR(255) NOT NULL) ENGINE = MYISAM ;""")
577
578         dbconn.execute("INSERT INTO `metadata` (`key`, `value`) VALUES ('key', '%s') " % (key))
579         im = Module(name="_internal", version="1.0", heading="Data Received", order=0)
580         t = Field(name="timestamp", module=im, type="integer", heading="Timestamp",
581                   headingAlign="center", align="right", 
582                   description="Time at which data were received by Owl server (seconds since the epoch)")
583         im.fields.append(t)
584         d = Field(name="date", module=im, type="string", heading="Date",
585                   headingAlign="center", align="right",
586                   description="Time at which data were received by Owl server")
587         im.fields.append(d)
588         status = _register_module(req, db, im)
589         if status:
590             debug(req, "register of %s failed, returning ERROR" % ('_internal'))
591             return False
592
593         # If an owner wasn't specified then anyone can manipulate the database. This is
594         # for backwards compatiblity with the non-ACL version of Owl.
595         if owner == None:
596             owner = '*'
597         owlacl.create_acl(dbname, owner)
598         return True
599     finally:
600         if dbconn:
601             dbconn.close()
602
603 def _delete_module(req, db, module):
604     global manager
605     dbname = owl_dbname(db)
606     dbconn = DB(name=dbname)
607     debug(req, "Deleting module '%s'" % (module))
608     cursor = dbconn.execute("SELECT field FROM `columns` WHERE `module` = '%s'" % (module))
609     for row in cursor.fetchall():
610         field = row[0]
611         dbconn.execute("ALTER TABLE `nodes` DROP `%s`" % (field))
612     dbconn.execute("DELETE FROM `columns` WHERE `module` = '%s'" % (module))
613     dbconn.execute("DELETE FROM `modules` WHERE `module` = '%s'" % (module))
614
615 def _register_module(req, db, module): 
616     dbconn = None
617     try:
618         dbname = owl_dbname(db)
619         dbconn = DB(name=dbname)
620         debug(req, "Registering module '%s'" % (module.name))
621         debug(req, "Checking module version")
622         cursor = dbconn.execute("SELECT VERSION FROM `modules` WHERE `module` = '%s'" % 
623                                 (module.name))
624         row = cursor.fetchone()
625         if row != None:
626             version = str(row[0])
627             if LooseVersion(module.version) < LooseVersion(version) :
628                 debug(req, "ERROR version %s is less than registered version %s" % 
629                 (module.version, version))
630                 req.write("ERROR version of module %s %s is less than registered version %s" % 
631                         (module.name, module.version, version))
632                 return 1
633             elif LooseVersion(module.version) > LooseVersion(version) :
634                 # Remove the old version information
635                 debug(req, "Deleting old module information")
636                 cursor = dbconn.execute("SELECT field FROM `columns` WHERE `module` = '%s'" % 
637                                         (module.name))
638                 for row in cursor.fetchall():
639                     field = row[0]
640                     dbconn.execute("ALTER TABLE `nodes` DROP `%s`" % (field))
641                 dbconn.execute("DELETE FROM `columns` WHERE `module` = '%s'" % (module.name))
642                 dbconn.execute("DELETE FROM `modules` WHERE `module` = '%s'" % (module.name))
643                 addit = True
644             else:
645                 # Same version, do nothing. Maybe we should verify that nothing has changed?
646                 debug(req, "Same version")
647                 addit = False
648         else:
649             debug(req, "Module does not exist")
650             addit = True
651         if addit:
652             debug(req, 'Adding module "%s"' % (module.name))
653             fields = []
654             for f in module.fields:
655                 fields.append(f.name)
656                 debug(req, "Adding %s to nodes table" % (f.fname))
657                 if f.type == "string":
658                     type = "VARCHAR(255)"
659                 elif f.type == 'integer':
660                     type = 'INT'
661                 else:
662                     req.write('ERROR field "%s" has unsupported type "%s"' % (f.fname, f.type)) 
663                     debug(req, "ERROR field %s has unsupported type %s" % (f.fname, f.type)) 
664                     return 1
665                 if f.stats == None:
666                     stats = ''
667                 else:
668                     stats =  ','.join(f.stats)
669                 dbconn.execute("ALTER TABLE `nodes` ADD `%s` %s NOT NULL" % (f.fname, type))
670                 dbconn.execute("""INSERT INTO `columns` (`field`, `module`, `heading`, `headingAlign`,`align`,`stats`, `description`, `type`) VALUES 
671                     ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')""" % (f.fname, str(f.module), str(f.heading), str(f.headingAlign), str(f.align), stats, str(f.description), f.type))
672             debug(req, "Inserting module %s into modules table" % (module.name))
673             fieldStr = ",".join(fields)
674             dbconn.execute("""INSERT INTO `modules` (`module`, `order`, `version`, `fields`, `heading`) VALUES 
675                     ('%s', '%d', '%s', '%s', '%s')""" % (str(module.name), module.order, str(module.version), fieldStr, str(module.heading)))
676     finally:
677         if dbconn:
678             dbconn.close()
679     debug(req, "Done registering module")
680     return 0
681
682 # Create databases. This is invoked by the Owl client during initialization.
683
684 def create(req, owner=None, key=None):
685     global protocol, request
686
687     request = req
688     debug(req, "create")
689     req.content_type = "text/plain"
690     try:
691         req_protocol = req.form.getfirst("protocol")
692         dbs = req.form.getlist("db")
693     except:
694         msg = "error parsing form"
695         debug(req, msg)
696         req.write("ERROR " + msg)
697         req.write(traceback.format_exc())
698         return
699     if req_protocol != protocol:
700         msg = "Wrong OWL protocol version: is %s, should be %s" % (req_protocol, protocol)
701         debug(req, msg)
702         req.write("ERROR " + msg)
703         return
704
705     for db in dbs:
706         _create_db(req, db, key=key, owner=owner)
707     debug(req, "create done")
708     req.write("OK")
709
710 #
711 # Register a module in the databases. For backwards compatibility create the databases 
712 # if necessary. This is invoked by the Owl client during initialization.
713 #
714
715 def register(req):
716     global protocol, request
717
718     request = req
719     debug(req, "register")
720     req.content_type = "text/plain"
721     try:
722         req_protocol = req.form.getfirst("protocol")
723         module = req.form.getfirst("module")
724         version = req.form.getfirst("version")
725         order = req.form.getfirst("order", 50)
726         modHeading = req.form.getfirst("heading", module)
727         databases = req.form.getlist("db")
728         fields = req.form.getfirst("fields")
729         principal = req.form.getfirst("auth", '')
730     except:
731         msg = "error parsing form"
732         debug(req, msg)
733         req.write("ERROR " + msg)
734         req.write(traceback.format_exc())
735         return
736     if req_protocol != protocol:
737         msg = "Wrong OWL protocol version: is %s, should be %s" % (req_protocol, protocol)
738         debug(req, msg)
739         req.write("ERROR " + msg)
740         return
741     m = Module(name=module, version=version, order=order+100, heading=modHeading)
742     for field in fields.split(','):
743         f = Field(name=field, module=m)
744         try:
745             f.type = req.form.getfirst(f.fname + ".type")
746             f.heading = req.form.getfirst(f.fname + ".heading", field)
747             f.headingAlign = req.form.getfirst(f.fname + ".headingAlign")
748             f.align = req.form.getfirst(f.fname + ".align")
749             f.stats = req.form.getlist(f.fname + ".stats")
750             f.description = req.form.getfirst(f.fname + ".description", "None")
751         except:
752             msg = "error parsing form"
753             debug(req, msg)
754             req.write("ERROR " + msg)
755             req.write(traceback.format_exc())
756             return
757         m.fields.append(f)
758         debug(req, 'Adding field "%s" to module "%s"' % (f.fname, m.name))
759     for db in databases:
760         debug(req, "registering %s in %s" % (module, db))
761         dbname = owl_dbname(db)
762         _create_db(req, db, owner=principal)
763         if not _authorized(dbname, principal,'write'):
764             return
765         status = _register_module(req, db, m)
766         if status:
767             debug(req, "register of %s failed, returning ERROR" % (module))
768             return
769     debug(req, "register done")
770     req.write("OK")
771
772 def delete(req):
773     global protocol, request, manager
774
775     request = req
776     debug(req, "delete")
777     debug(req, str(req.form.items()))
778     req.content_type = "text/plain"
779     try:
780         req_protocol = req.form.getfirst("protocol")
781         module = req.form.getfirst("module")
782         databases = req.form.getlist("db")
783         principal = req.form.getfirst("auth", '')
784     except:
785         msg = "error parsing form"
786         debug(req, msg)
787         req.write("ERROR " + msg)
788         req.write(traceback.format_exc())
789         request = None
790         return
791     if req_protocol != protocol:
792         msg = "Wrong OWL protocol version: is %s, should be %s" % (req_protocol, protocol)
793         debug(req, msg)
794         req.write("ERROR " + msg)
795         request = None
796         return
797     for db in databases:
798         debug(req, "deleting %s in %s" % (module, db))
799         dbname = owl_dbname(db)
800         if not _authorized(dbname, principal, 'write'):
801             return
802         _delete_module(req, db, module)
803     debug(req, "delete done")
804     req.write("OK")
805
806 #
807 # Update the information in the databases. This is invoked by the Owl client periodically.
808 #
809
810 def update(req):
811     global protocol, request
812
813     request = req
814     dbconn = None
815     try:
816         debug(req, "update")
817         req.content_type = "text/plain"
818         try:
819             req_protocol = req.form.getfirst("protocol")
820             databases = req.form.getlist("db")
821             principal = req.form.getfirst("auth", '')
822         except:
823             msg = "error parsing form"
824             debug(req, msg)
825             req.write("ERROR " + msg)
826             #req.write(traceback.format_exc())
827             return
828         if req_protocol != protocol:
829             msg = "Wrong OWL protocol version: is %s, should be %s" % (req_protocol, protocol)
830             debug(req, msg)
831             req.write("ERROR " + msg)
832             return
833         timestamp = time.time()
834         #date = time.asctime(time.gmtime(timestamp))
835         date = time.strftime('%a %b %d %X UTC %Y',time.gmtime(timestamp))
836         for db in databases:
837             dbname = owl_dbname(db)
838             if not _authorized(dbname, principal, 'write'):
839                 continue
840             try:
841                 dbconn = DB(name=dbname)
842             except MySQLdb.Error, e:
843                 req.write("No database \"%s\"" % (db))
844                 return
845             fields = req.form.keys()
846             fields.remove("protocol")
847             try:
848                 fields.remove("auth")
849             except:
850                 pass
851             while (fields.count("db") > 0):
852                 fields.remove("db")
853             try:
854                 cursor = dbconn.execute("SELECT `value` FROM `metadata` WHERE `key` = 'key'")
855                 row = cursor.fetchone()
856                 key = str(row[0])
857
858                 if key not in fields:
859                     _error("update must contain key field '%s' for database '%s'" % (key, db))
860                     return
861             except Exception, e:
862                 debug(req, str(e))
863                 _error("unable to determine key field for database '%s'" % (db))
864                 return
865
866             id = req.form.getfirst(key, 'basic.id')
867             valid = []
868             cursor = dbconn.execute("SELECT field FROM `columns`")
869             rows = cursor.fetchall()
870             for row in rows:
871                 valid.append(row[0])
872             assignments = []
873             for f in fields:
874                 if f not in valid:
875                     debug(req, "ERROR invalid field '%s'" % f)
876                     req.write("ERROR invalid field '%s'" % f)
877                     dbconn.close()
878                     request = None
879                     return
880                 assignments.append("`%s` = '%s'" % (f, req.form.getfirst(f,"")))
881             assignments.append("`_internal.timestamp` = '%d'" % (timestamp))
882             assignments.append("`_internal.date` = '%s'" % (date))
883             assignStr = ",".join(assignments)
884             cursor = dbconn.execute("SELECT * FROM `nodes` WHERE `%s` = '%s'" % (key,id))
885             if cursor.rowcount > 0:
886                 dbconn.execute("UPDATE `nodes` SET %s WHERE `%s` = '%s'" % (assignStr,key,id))
887             else: 
888                 dbconn.execute("INSERT INTO `nodes` SET %s" % (assignStr))
889         req.write("OK")
890         debug(req, "update done")
891     finally:
892         if dbconn:
893             dbconn.close()
894
895
896 def delete_db(req, db, auth=''):
897     global protocol, request
898
899     request = req
900     dbconn = None
901     try:
902         debug(req, "delete %s" % (db))
903         dbname = owl_dbname(db)
904         req.content_type = "text/plain"
905         try:
906             req_protocol = req.form.getfirst("protocol")
907         except:
908             msg = "error parsing form"
909             debug(req, msg)
910             req.write("ERROR " + msg)
911             return
912         if req_protocol != protocol:
913             msg = "Wrong OWL protocol version: is %s, should be %s" % (req_protocol, protocol)
914             debug(req, msg)
915             req.write("ERROR " + msg)
916             return
917         if db != None:
918             if not _authorized(dbname, auth, 'owner'):
919                 return
920             try:
921                 file = "/root/mysql-root-pass.txt"
922                 fd = open(file, "r")
923                 password = fd.readline().rstrip()
924                 fd.close()
925             except:
926                 error('Unable to read mysql root password from "%s"' % (file))
927                 raise
928             _delete_db(dbname)
929
930         req.write("OK")
931         request = None
932     finally:
933         if dbconn != None:
934             dbconn.close()
935
936 def _delete_db(dbname):
937     dbconn = None
938     try:
939         dbconn = DB(name="information_schema")
940         cursor = dbconn.execute("SELECT * FROM `SCHEMATA` WHERE `SCHEMA_NAME` = '%s'" % (dbname))
941         row = cursor.fetchone()
942         if row != None:
943             dbconn=DB(name=dbname, user="root", passwd=password)
944             dbconn.execute("DROP DATABASE `%s`" % (dbname))
945         dbconn.close()
946         # destroy the acls for this database
947         owlacl.delete_acl_no_check(dbname)
948     finally:
949         if dbconn != None:
950             dbconn.close()
951
952 def reset_db(req, db=None, auth=''):
953     global request
954
955     request = req
956     dbconn = None
957     try:
958         if db:
959             debug(req, "reset %s" % (db))
960             dbname = owl_dbname(db)
961             if not _authorized(dbname, auth, 'write'):
962                 return
963             try:
964                 dbconn = DB(name=dbname)
965             except:
966                 _error("No database \"%s\"" % (db))
967                 return
968             dbconn.execute("TRUNCATE `nodes`")
969             dbconn.close()
970         req.write("OK");
971     finally:
972         if dbconn:
973             dbconn.close()
974
975
976 def get_acl(req, db=None, auth=''):
977     global request
978
979     request = req
980     if db == None:
981         _error("No database specified")
982         return
983     dbname = owl_dbname(db)
984     debug(req, "get_acl %s" % (db))
985     req.content_type = "text/plain"
986     try:
987         acl = owlacl.get_acl(dbname, auth)
988         if acl:
989             req.write("OK ")
990             for p in acl.rights:
991                 req.write("'%s' %s\n" % (p, acl.rights[p]))
992         else:
993             _error("Permission denied")
994     except:
995         _error("No such database '%s'" % (db))
996         return
997
998 def delete_right(req, db=None, auth='', principal=None, right=None):
999
1000     global request
1001
1002     try:
1003         request = req
1004         dbname = owl_dbname(db)
1005         if db == None:
1006             _error("No database specified")
1007             return
1008         if principal == None:
1009             _error("No principal specified")
1010             return
1011         if right == None:
1012             _error("No right specified")
1013             return
1014         dbname = owl_dbname(db)
1015         debug(req, "delete_right %s %s %s %s" % (db, auth, principal, right))
1016         req.content_type = "text/plain"
1017         owlacl.delete_right(dbname, auth, principal, right)
1018         req.write("OK");
1019     except Exception, e:
1020         _error(str(e))