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

Last change on this file since 733 was 693, checked in by price, 16 years ago

don't give 500 for mere stdout on a redirect

  • Property svn:executable set to *
File size: 26.0 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 sha
10import simplejson
11import sys
12import time
13import urllib
14import random
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
44
45def pathSplit(path):
46    if path.startswith('/'):
47        path = path[1:]
48    i = path.find('/')
49    if i == -1:
50        i = len(path)
51    return path[:i], path[i:]
52
53class Checkpoint:
54    def __init__(self):
55        self.start_time = time.time()
56        self.checkpoints = []
57
58    def checkpoint(self, s):
59        self.checkpoints.append((s, time.time()))
60
61    def __str__(self):
62        return ('Timing info:\n%s\n' %
63                '\n'.join(['%s: %s' % (d, t - self.start_time) for
64                           (d, t) in self.checkpoints]))
65
66checkpoint = Checkpoint()
67
68def jquote(string):
69    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
70
71def helppopup(subj):
72    """Return HTML code for a (?) link to a specified help topic"""
73    return ('<span class="helplink"><a href="help?' +
74            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
75            +'" target="_blank" ' +
76            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
77
78def makeErrorPre(old, addition):
79    if addition is None:
80        return
81    if old:
82        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
83    else:
84        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
85
86Template.sipb_xen_database = sipb_xen_database
87Template.helppopup = staticmethod(helppopup)
88Template.err = None
89
90class JsonDict:
91    """Class to store a dictionary that will be converted to JSON"""
92    def __init__(self, **kws):
93        self.data = kws
94        if 'err' in kws:
95            err = kws['err']
96            del kws['err']
97            self.addError(err)
98
99    def __str__(self):
100        return simplejson.dumps(self.data)
101
102    def addError(self, text):
103        """Add stderr text to be displayed on the website."""
104        self.data['err'] = \
105            makeErrorPre(self.data.get('err'), text)
106
107class Defaults:
108    """Class to store default values for fields."""
109    memory = 256
110    disk = 4.0
111    cdrom = ''
112    autoinstall = ''
113    name = ''
114    description = ''
115    type = 'linux-hvm'
116
117    def __init__(self, max_memory=None, max_disk=None, **kws):
118        if max_memory is not None:
119            self.memory = min(self.memory, max_memory)
120        if max_disk is not None:
121            self.max_disk = min(self.disk, max_disk)
122        for key in kws:
123            setattr(self, key, kws[key])
124
125
126
127DEFAULT_HEADERS = {'Content-Type': 'text/html'}
128
129def invalidInput(op, username, fields, err, emsg):
130    """Print an error page when an InvalidInput exception occurs"""
131    d = dict(op=op, user=username, err_field=err.err_field,
132             err_value=str(err.err_value), stderr=emsg,
133             errorMessage=str(err))
134    return templates.invalid(searchList=[d])
135
136def hasVnc(status):
137    """Does the machine with a given status list support VNC?"""
138    if status is None:
139        return False
140    for l in status:
141        if l[0] == 'device' and l[1][0] == 'vfb':
142            d = dict(l[1][1:])
143            return 'location' in d
144    return False
145
146def parseCreate(username, state, fields):
147    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
148    validate = validation.Validate(username, state, strict=True, **kws)
149    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
150                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
151                cdrom=getattr(validate, 'cdrom', None),
152                autoinstall=getattr(validate, 'autoinstall', None))
153
154def create(username, state, path, fields):
155    """Handler for create requests."""
156    try:
157        parsed_fields = parseCreate(username, state, fields)
158        machine = controls.createVm(username, state, **parsed_fields)
159    except InvalidInput, err:
160        pass
161    else:
162        err = None
163    state.clear() #Changed global state
164    d = getListDict(username, state)
165    d['err'] = err
166    if err:
167        for field in fields.keys():
168            setattr(d['defaults'], field, fields.getfirst(field))
169    else:
170        d['new_machine'] = parsed_fields['name']
171    return templates.list(searchList=[d])
172
173
174def getListDict(username, state):
175    """Gets the list of local variables used by list.tmpl."""
176    checkpoint.checkpoint('Starting')
177    machines = state.machines
178    checkpoint.checkpoint('Got my machines')
179    on = {}
180    has_vnc = {}
181    xmlist = state.xmlist
182    checkpoint.checkpoint('Got uptimes')
183    can_clone = 'ice3' not in state.xmlist_raw
184    for m in machines:
185        if m not in xmlist:
186            has_vnc[m] = 'Off'
187            m.uptime = None
188        else:
189            m.uptime = xmlist[m]['uptime']
190            if xmlist[m]['console']:
191                has_vnc[m] = True
192            elif m.type.hvm:
193                has_vnc[m] = "WTF?"
194            else:
195                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
196    max_memory = validation.maxMemory(username, state)
197    max_disk = validation.maxDisk(username)
198    checkpoint.checkpoint('Got max mem/disk')
199    defaults = Defaults(max_memory=max_memory,
200                        max_disk=max_disk,
201                        owner=username,
202                        cdrom='gutsy-i386')
203    checkpoint.checkpoint('Got defaults')
204    def sortkey(machine):
205        return (machine.owner != username, machine.owner, machine.name)
206    machines = sorted(machines, key=sortkey)
207    d = dict(user=username,
208             cant_add_vm=validation.cantAddVm(username, state),
209             max_memory=max_memory,
210             max_disk=max_disk,
211             defaults=defaults,
212             machines=machines,
213             has_vnc=has_vnc,
214             can_clone=can_clone)
215    return d
216
217def listVms(username, state, path, fields):
218    """Handler for list requests."""
219    checkpoint.checkpoint('Getting list dict')
220    d = getListDict(username, state)
221    checkpoint.checkpoint('Got list dict')
222    return templates.list(searchList=[d])
223
224def vnc(username, state, path, fields):
225    """VNC applet page.
226
227    Note that due to same-domain restrictions, the applet connects to
228    the webserver, which needs to forward those requests to the xen
229    server.  The Xen server runs another proxy that (1) authenticates
230    and (2) finds the correct port for the VM.
231
232    You might want iptables like:
233
234    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
235      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
236    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
237      --dport 10003 -j SNAT --to-source 18.187.7.142
238    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
239      --dport 10003 -j ACCEPT
240
241    Remember to enable iptables!
242    echo 1 > /proc/sys/net/ipv4/ip_forward
243    """
244    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
245
246    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
247
248    data = {}
249    data["user"] = username
250    data["machine"] = machine.name
251    data["expires"] = time.time()+(5*60)
252    pickled_data = cPickle.dumps(data)
253    m = hmac.new(TOKEN_KEY, digestmod=sha)
254    m.update(pickled_data)
255    token = {'data': pickled_data, 'digest': m.digest()}
256    token = cPickle.dumps(token)
257    token = base64.urlsafe_b64encode(token)
258    if controls.listHost(machine) == 'sx-blade-2.mit.edu':
259        port = 10004
260    else:
261        port = 10003
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.