source: trunk/packages/sipb-xen-www/code/main.py @ 770

Last change on this file since 770 was 770, checked in by geofft, 16 years ago

sipb-xen-www: Use invirt.config to pick the VNC proxy port.

  • Property svn:executable set to *
File size: 26.1 KB
Line 
1#!/usr/bin/python
2"""Main CGI script for web interface"""
3
4import base64
5import cPickle
6import cgi
7import datetime
8import hmac
9import random
10import sha
11import simplejson
12import sys
13import time
14import urllib
15from StringIO import StringIO
16
17def revertStandardError():
18    """Move stderr to stdout, and return the contents of the old stderr."""
19    errio = sys.stderr
20    if not isinstance(errio, StringIO):
21        return ''
22    sys.stderr = sys.stdout
23    errio.seek(0)
24    return errio.read()
25
26def printError():
27    """Revert stderr to stdout, and print the contents of stderr"""
28    if isinstance(sys.stderr, StringIO):
29        print revertStandardError()
30
31if __name__ == '__main__':
32    import atexit
33    atexit.register(printError)
34
35import templates
36from Cheetah.Template import Template
37import sipb_xen_database
38from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
39import validation
40import cache_acls
41from webcommon import InvalidInput, CodeError, State
42import controls
43from getafsgroups import getAfsGroupMembers
44import invirt.config
45invirt_config = invirt.config.load()
46
47def pathSplit(path):
48    if path.startswith('/'):
49        path = path[1:]
50    i = path.find('/')
51    if i == -1:
52        i = len(path)
53    return path[:i], path[i:]
54
55class Checkpoint:
56    def __init__(self):
57        self.start_time = time.time()
58        self.checkpoints = []
59
60    def checkpoint(self, s):
61        self.checkpoints.append((s, time.time()))
62
63    def __str__(self):
64        return ('Timing info:\n%s\n' %
65                '\n'.join(['%s: %s' % (d, t - self.start_time) for
66                           (d, t) in self.checkpoints]))
67
68checkpoint = Checkpoint()
69
70def jquote(string):
71    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
72
73def helppopup(subj):
74    """Return HTML code for a (?) link to a specified help topic"""
75    return ('<span class="helplink"><a href="help?' +
76            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
77            +'" target="_blank" ' +
78            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
79
80def makeErrorPre(old, addition):
81    if addition is None:
82        return
83    if old:
84        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
85    else:
86        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
87
88Template.sipb_xen_database = sipb_xen_database
89Template.helppopup = staticmethod(helppopup)
90Template.err = None
91
92class JsonDict:
93    """Class to store a dictionary that will be converted to JSON"""
94    def __init__(self, **kws):
95        self.data = kws
96        if 'err' in kws:
97            err = kws['err']
98            del kws['err']
99            self.addError(err)
100
101    def __str__(self):
102        return simplejson.dumps(self.data)
103
104    def addError(self, text):
105        """Add stderr text to be displayed on the website."""
106        self.data['err'] = \
107            makeErrorPre(self.data.get('err'), text)
108
109class Defaults:
110    """Class to store default values for fields."""
111    memory = 256
112    disk = 4.0
113    cdrom = ''
114    autoinstall = ''
115    name = ''
116    description = ''
117    type = 'linux-hvm'
118
119    def __init__(self, max_memory=None, max_disk=None, **kws):
120        if max_memory is not None:
121            self.memory = min(self.memory, max_memory)
122        if max_disk is not None:
123            self.max_disk = min(self.disk, max_disk)
124        for key in kws:
125            setattr(self, key, kws[key])
126
127
128
129DEFAULT_HEADERS = {'Content-Type': 'text/html'}
130
131def invalidInput(op, username, fields, err, emsg):
132    """Print an error page when an InvalidInput exception occurs"""
133    d = dict(op=op, user=username, err_field=err.err_field,
134             err_value=str(err.err_value), stderr=emsg,
135             errorMessage=str(err))
136    return templates.invalid(searchList=[d])
137
138def hasVnc(status):
139    """Does the machine with a given status list support VNC?"""
140    if status is None:
141        return False
142    for l in status:
143        if l[0] == 'device' and l[1][0] == 'vfb':
144            d = dict(l[1][1:])
145            return 'location' in d
146    return False
147
148def parseCreate(username, state, fields):
149    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
150    validate = validation.Validate(username, state, strict=True, **kws)
151    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
152                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
153                cdrom=getattr(validate, 'cdrom', None),
154                autoinstall=getattr(validate, 'autoinstall', None))
155
156def create(username, state, path, fields):
157    """Handler for create requests."""
158    try:
159        parsed_fields = parseCreate(username, state, fields)
160        machine = controls.createVm(username, state, **parsed_fields)
161    except InvalidInput, err:
162        pass
163    else:
164        err = None
165    state.clear() #Changed global state
166    d = getListDict(username, state)
167    d['err'] = err
168    if err:
169        for field in fields.keys():
170            setattr(d['defaults'], field, fields.getfirst(field))
171    else:
172        d['new_machine'] = parsed_fields['name']
173    return templates.list(searchList=[d])
174
175
176def getListDict(username, state):
177    """Gets the list of local variables used by list.tmpl."""
178    checkpoint.checkpoint('Starting')
179    machines = state.machines
180    checkpoint.checkpoint('Got my machines')
181    on = {}
182    has_vnc = {}
183    xmlist = state.xmlist
184    checkpoint.checkpoint('Got uptimes')
185    can_clone = 'ice3' not in state.xmlist_raw
186    for m in machines:
187        if m not in xmlist:
188            has_vnc[m] = 'Off'
189            m.uptime = None
190        else:
191            m.uptime = xmlist[m]['uptime']
192            if xmlist[m]['console']:
193                has_vnc[m] = True
194            elif m.type.hvm:
195                has_vnc[m] = "WTF?"
196            else:
197                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
198    max_memory = validation.maxMemory(username, state)
199    max_disk = validation.maxDisk(username)
200    checkpoint.checkpoint('Got max mem/disk')
201    defaults = Defaults(max_memory=max_memory,
202                        max_disk=max_disk,
203                        owner=username,
204                        cdrom='gutsy-i386')
205    checkpoint.checkpoint('Got defaults')
206    def sortkey(machine):
207        return (machine.owner != username, machine.owner, machine.name)
208    machines = sorted(machines, key=sortkey)
209    d = dict(user=username,
210             cant_add_vm=validation.cantAddVm(username, state),
211             max_memory=max_memory,
212             max_disk=max_disk,
213             defaults=defaults,
214             machines=machines,
215             has_vnc=has_vnc,
216             can_clone=can_clone)
217    return d
218
219def listVms(username, state, path, fields):
220    """Handler for list requests."""
221    checkpoint.checkpoint('Getting list dict')
222    d = getListDict(username, state)
223    checkpoint.checkpoint('Got list dict')
224    return templates.list(searchList=[d])
225
226def vnc(username, state, path, fields):
227    """VNC applet page.
228
229    Note that due to same-domain restrictions, the applet connects to
230    the webserver, which needs to forward those requests to the xen
231    server.  The Xen server runs another proxy that (1) authenticates
232    and (2) finds the correct port for the VM.
233
234    You might want iptables like:
235
236    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
237      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
238    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
239      --dport 10003 -j SNAT --to-source 18.187.7.142
240    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
241      --dport 10003 -j ACCEPT
242
243    Remember to enable iptables!
244    echo 1 > /proc/sys/net/ipv4/ip_forward
245    """
246    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
247
248    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
249
250    data = {}
251    data["user"] = username
252    data["machine"] = machine.name
253    data["expires"] = time.time()+(5*60)
254    pickled_data = cPickle.dumps(data)
255    m = hmac.new(TOKEN_KEY, digestmod=sha)
256    m.update(pickled_data)
257    token = {'data': pickled_data, 'digest': m.digest()}
258    token = cPickle.dumps(token)
259    token = base64.urlsafe_b64encode(token)
260    port = 10003 + [config_host["hostname"] for config_host in invirt_config["hosts"]
261        ].index(controls.listHost(machine))
262
263    status = controls.statusInfo(machine)
264    has_vnc = hasVnc(status)
265
266    d = dict(user=username,
267             on=status,
268             has_vnc=has_vnc,
269             machine=machine,
270             hostname=state.environ.get('SERVER_NAME', 'localhost'),
271             port=port,
272             authtoken=token)
273    return templates.vnc(searchList=[d])
274
275def getHostname(nic):
276    """Find the hostname associated with a NIC.
277
278    XXX this should be merged with the similar logic in DNS and DHCP.
279    """
280    if nic.hostname and '.' in nic.hostname:
281        return nic.hostname
282    elif nic.machine:
283        return nic.machine.name + '.xvm.mit.edu'
284    else:
285        return None
286
287
288def getNicInfo(data_dict, machine):
289    """Helper function for info, get data on nics for a machine.
290
291    Modifies data_dict to include the relevant data, and returns a list
292    of (key, name) pairs to display "name: data_dict[key]" to the user.
293    """
294    data_dict['num_nics'] = len(machine.nics)
295    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
296                           ('nic%s_mac', 'NIC %s MAC Addr'),
297                           ('nic%s_ip', 'NIC %s IP'),
298                           ]
299    nic_fields = []
300    for i in range(len(machine.nics)):
301        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
302        if not i:
303            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
304        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
305        data_dict['nic%s_ip' % i] = machine.nics[i].ip
306    if len(machine.nics) == 1:
307        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
308    return nic_fields
309
310def getDiskInfo(data_dict, machine):
311    """Helper function for info, get data on disks for a machine.
312
313    Modifies data_dict to include the relevant data, and returns a list
314    of (key, name) pairs to display "name: data_dict[key]" to the user.
315    """
316    data_dict['num_disks'] = len(machine.disks)
317    disk_fields_template = [('%s_size', '%s size')]
318    disk_fields = []
319    for disk in machine.disks:
320        name = disk.guest_device_name
321        disk_fields.extend([(x % name, y % name) for x, y in
322                            disk_fields_template])
323        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
324    return disk_fields
325
326def command(username, state, path, fields):
327    """Handler for running commands like boot and delete on a VM."""
328    back = fields.getfirst('back')
329    try:
330        d = controls.commandResult(username, state, fields)
331        if d['command'] == 'Delete VM':
332            back = 'list'
333    except InvalidInput, err:
334        if not back:
335            raise
336        print >> sys.stderr, err
337        result = err
338    else:
339        result = 'Success!'
340        if not back:
341            return templates.command(searchList=[d])
342    if back == 'list':
343        state.clear() #Changed global state
344        d = getListDict(username, state)
345        d['result'] = result
346        return templates.list(searchList=[d])
347    elif back == 'info':
348        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
349        return ({'Status': '303 See Other',
350                 'Location': 'info?machine_id=%d' % machine.machine_id},
351                "You shouldn't see this message.")
352    else:
353        raise InvalidInput('back', back, 'Not a known back page.')
354
355def modifyDict(username, state, fields):
356    """Modify a machine as specified by CGI arguments.
357
358    Return a list of local variables for modify.tmpl.
359    """
360    olddisk = {}
361    transaction = ctx.current.create_transaction()
362    try:
363        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
364        validate = validation.Validate(username, state, **kws)
365        machine = validate.machine
366        oldname = machine.name
367
368        if hasattr(validate, 'memory'):
369            machine.memory = validate.memory
370
371        if hasattr(validate, 'vmtype'):
372            machine.type = validate.vmtype
373
374        if hasattr(validate, 'disksize'):
375            disksize = validate.disksize
376            disk = machine.disks[0]
377            if disk.size != disksize:
378                olddisk[disk.guest_device_name] = disksize
379                disk.size = disksize
380                ctx.current.save(disk)
381
382        update_acl = False
383        if hasattr(validate, 'owner') and validate.owner != machine.owner:
384            machine.owner = validate.owner
385            update_acl = True
386        if hasattr(validate, 'name'):
387            machine.name = validate.name
388        if hasattr(validate, 'description'):
389            machine.description = validate.description
390        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
391            machine.administrator = validate.admin
392            update_acl = True
393        if hasattr(validate, 'contact'):
394            machine.contact = validate.contact
395
396        ctx.current.save(machine)
397        if update_acl:
398            print >> sys.stderr, machine, machine.administrator
399            cache_acls.refreshMachine(machine)
400        transaction.commit()
401    except:
402        transaction.rollback()
403        raise
404    for diskname in olddisk:
405        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
406    if hasattr(validate, 'name'):
407        controls.renameMachine(machine, oldname, validate.name)
408    return dict(user=username,
409                command="modify",
410                machine=machine)
411
412def modify(username, state, path, fields):
413    """Handler for modifying attributes of a machine."""
414    try:
415        modify_dict = modifyDict(username, state, fields)
416    except InvalidInput, err:
417        result = None
418        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
419    else:
420        machine = modify_dict['machine']
421        result = 'Success!'
422        err = None
423    info_dict = infoDict(username, state, machine)
424    info_dict['err'] = err
425    if err:
426        for field in fields.keys():
427            setattr(info_dict['defaults'], field, fields.getfirst(field))
428    info_dict['result'] = result
429    return templates.info(searchList=[info_dict])
430
431
432def helpHandler(username, state, path, fields):
433    """Handler for help messages."""
434    simple = fields.getfirst('simple')
435    subjects = fields.getlist('subject')
436
437    help_mapping = {'ParaVM Console': """
438ParaVM machines do not support local console access over VNC.  To
439access the serial console of these machines, you can SSH with Kerberos
440to console.xvm.mit.edu, using the name of the machine as your
441username.""",
442                    'HVM/ParaVM': """
443HVM machines use the virtualization features of the processor, while
444ParaVM machines use Xen's emulation of virtualization features.  You
445want an HVM virtualized machine.""",
446                    'CPU Weight': """
447Don't ask us!  We're as mystified as you are.""",
448                    'Owner': """
449The owner field is used to determine <a
450href="help?subject=Quotas">quotas</a>.  It must be the name of a
451locker that you are an AFS administrator of.  In particular, you or an
452AFS group you are a member of must have AFS rlidwka bits on the
453locker.  You can check who administers the LOCKER locker using the
454commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
455href="help?subject=Administrator">administrator</a>.""",
456                    'Administrator': """
457The administrator field determines who can access the console and
458power on and off the machine.  This can be either a user or a moira
459group.""",
460                    'Quotas': """
461Quotas are determined on a per-locker basis.  Each locker may have a
462maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
463active machines.""",
464                    'Console': """
465<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
466setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
467your machine will run just fine, but the applet's display of the
468console will suffer artifacts.
469"""
470                    }
471
472    if not subjects:
473        subjects = sorted(help_mapping.keys())
474
475    d = dict(user=username,
476             simple=simple,
477             subjects=subjects,
478             mapping=help_mapping)
479
480    return templates.help(searchList=[d])
481
482
483def badOperation(u, s, p, e):
484    """Function called when accessing an unknown URI."""
485    return ({'Status': '404 Not Found'}, 'Invalid operation.')
486
487def infoDict(username, state, machine):
488    """Get the variables used by info.tmpl."""
489    status = controls.statusInfo(machine)
490    checkpoint.checkpoint('Getting status info')
491    has_vnc = hasVnc(status)
492    if status is None:
493        main_status = dict(name=machine.name,
494                           memory=str(machine.memory))
495        uptime = None
496        cputime = None
497    else:
498        main_status = dict(status[1:])
499        main_status['host'] = controls.listHost(machine)
500        start_time = float(main_status.get('start_time', 0))
501        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
502        cpu_time_float = float(main_status.get('cpu_time', 0))
503        cputime = datetime.timedelta(seconds=int(cpu_time_float))
504    checkpoint.checkpoint('Status')
505    display_fields = """name uptime memory state cpu_weight on_reboot
506     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
507    display_fields = [('name', 'Name'),
508                      ('description', 'Description'),
509                      ('owner', 'Owner'),
510                      ('administrator', 'Administrator'),
511                      ('contact', 'Contact'),
512                      ('type', 'Type'),
513                      'NIC_INFO',
514                      ('uptime', 'uptime'),
515                      ('cputime', 'CPU usage'),
516                      ('host', 'Hosted on'),
517                      ('memory', 'RAM'),
518                      'DISK_INFO',
519                      ('state', 'state (xen format)'),
520                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
521                      ('on_reboot', 'Action on VM reboot'),
522                      ('on_poweroff', 'Action on VM poweroff'),
523                      ('on_crash', 'Action on VM crash'),
524                      ('on_xend_start', 'Action on Xen start'),
525                      ('on_xend_stop', 'Action on Xen stop'),
526                      ('bootloader', 'Bootloader options'),
527                      ]
528    fields = []
529    machine_info = {}
530    machine_info['name'] = machine.name
531    machine_info['description'] = machine.description
532    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
533    machine_info['owner'] = machine.owner
534    machine_info['administrator'] = machine.administrator
535    machine_info['contact'] = machine.contact
536
537    nic_fields = getNicInfo(machine_info, machine)
538    nic_point = display_fields.index('NIC_INFO')
539    display_fields = (display_fields[:nic_point] + nic_fields +
540                      display_fields[nic_point+1:])
541
542    disk_fields = getDiskInfo(machine_info, machine)
543    disk_point = display_fields.index('DISK_INFO')
544    display_fields = (display_fields[:disk_point] + disk_fields +
545                      display_fields[disk_point+1:])
546
547    main_status['memory'] += ' MiB'
548    for field, disp in display_fields:
549        if field in ('uptime', 'cputime') and locals()[field] is not None:
550            fields.append((disp, locals()[field]))
551        elif field in machine_info:
552            fields.append((disp, machine_info[field]))
553        elif field in main_status:
554            fields.append((disp, main_status[field]))
555        else:
556            pass
557            #fields.append((disp, None))
558
559    checkpoint.checkpoint('Got fields')
560
561
562    max_mem = validation.maxMemory(machine.owner, state, machine, False)
563    checkpoint.checkpoint('Got mem')
564    max_disk = validation.maxDisk(machine.owner, machine)
565    defaults = Defaults()
566    for name in 'machine_id name description administrator owner memory contact'.split():
567        setattr(defaults, name, getattr(machine, name))
568    defaults.type = machine.type.type_id
569    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
570    checkpoint.checkpoint('Got defaults')
571    d = dict(user=username,
572             on=status is not None,
573             machine=machine,
574             defaults=defaults,
575             has_vnc=has_vnc,
576             uptime=str(uptime),
577             ram=machine.memory,
578             max_mem=max_mem,
579             max_disk=max_disk,
580             owner_help=helppopup("Owner"),
581             fields = fields)
582    return d
583
584def info(username, state, path, fields):
585    """Handler for info on a single VM."""
586    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
587    d = infoDict(username, state, machine)
588    checkpoint.checkpoint('Got infodict')
589    return templates.info(searchList=[d])
590
591def unauthFront(_, _2, _3, fields):
592    """Information for unauth'd users."""
593    return templates.unauth(searchList=[{'simple' : True}])
594
595def overlord(username, state, path, fields):
596    if path == '':
597        return ({'Status': '303 See Other',
598                 'Location': 'overlord/'},
599                "You shouldn't see this message.")
600    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
601        raise InvalidInput('username', username, 'Not an overlord.')
602    newstate = State(username, overlord=True)
603    newstate.environ = state.environ
604    return handler(username, newstate, path, fields)
605
606def throwError(_, __, ___, ____):
607    """Throw an error, to test the error-tracing mechanisms."""
608    raise RuntimeError("test of the emergency broadcast system")
609
610mapping = dict(list=listVms,
611               vnc=vnc,
612               command=command,
613               modify=modify,
614               info=info,
615               create=create,
616               help=helpHandler,
617               unauth=unauthFront,
618               overlord=overlord,
619               errortest=throwError)
620
621def printHeaders(headers):
622    """Print a dictionary as HTTP headers."""
623    for key, value in headers.iteritems():
624        print '%s: %s' % (key, value)
625    print
626
627def send_error_mail(subject, body):
628    import subprocess
629
630    to = 'xvm@mit.edu'
631    mail = """To: %s
632From: root@xvm.mit.edu
633Subject: %s
634
635%s
636""" % (to, subject, body)
637    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
638    p.stdin.write(mail)
639    p.stdin.close()
640    p.wait()
641
642def show_error(op, username, fields, err, emsg, traceback):
643    """Print an error page when an exception occurs"""
644    d = dict(op=op, user=username, fields=fields,
645             errorMessage=str(err), stderr=emsg, traceback=traceback)
646    details = templates.error_raw(searchList=[d])
647    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
648        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
649                        details)
650    d['details'] = details
651    return templates.error(searchList=[d])
652
653def getUser(environ):
654    """Return the current user based on the SSL environment variables"""
655    return environ.get('REMOTE_USER', None)
656
657def handler(username, state, path, fields):
658    operation, path = pathSplit(path)
659    if not operation:
660        operation = 'list'
661    print 'Starting', operation
662    fun = mapping.get(operation, badOperation)
663    return fun(username, state, path, fields)
664
665class App:
666    def __init__(self, environ, start_response):
667        self.environ = environ
668        self.start = start_response
669
670        self.username = getUser(environ)
671        self.state = State(self.username)
672        self.state.environ = environ
673
674        random.seed() #sigh
675
676    def __iter__(self):
677        start_time = time.time()
678        sipb_xen_database.clear_cache()
679        sys.stderr = StringIO()
680        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
681        operation = self.environ.get('PATH_INFO', '')
682        if not operation:
683            self.start("301 Moved Permanently", [('Location', './')])
684            return
685        if self.username is None:
686            operation = 'unauth'
687
688        try:
689            checkpoint.checkpoint('Before')
690            output = handler(self.username, self.state, operation, fields)
691            checkpoint.checkpoint('After')
692
693            headers = dict(DEFAULT_HEADERS)
694            if isinstance(output, tuple):
695                new_headers, output = output
696                headers.update(new_headers)
697            e = revertStandardError()
698            if e:
699                if hasattr(output, 'addError'):
700                    output.addError(e)
701                else:
702                    # This only happens on redirects, so it'd be a pain to get
703                    # the message to the user.  Maybe in the response is useful.
704                    output = output + '\n\nstderr:\n' + e
705            output_string =  str(output)
706            checkpoint.checkpoint('output as a string')
707        except Exception, err:
708            if not fields.has_key('js'):
709                if isinstance(err, InvalidInput):
710                    self.start('200 OK', [('Content-Type', 'text/html')])
711                    e = revertStandardError()
712                    yield str(invalidInput(operation, self.username, fields,
713                                           err, e))
714                    return
715            import traceback
716            self.start('500 Internal Server Error',
717                       [('Content-Type', 'text/html')])
718            e = revertStandardError()
719            s = show_error(operation, self.username, fields,
720                           err, e, traceback.format_exc())
721            yield str(s)
722            return
723        status = headers.setdefault('Status', '200 OK')
724        del headers['Status']
725        self.start(status, headers.items())
726        yield output_string
727        if fields.has_key('timedebug'):
728            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
729
730def constructor():
731    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
732    return App
733
734def main():
735    from flup.server.fcgi_fork import WSGIServer
736    WSGIServer(constructor()).run()
737
738if __name__ == '__main__':
739    main()
Note: See TracBrowser for help on using the repository browser.