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

Last change on this file since 642 was 640, checked in by ecprice, 16 years ago

Import random before seeding

  • Property svn:executable set to *
File size: 25.8 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
259    status = controls.statusInfo(machine)
260    has_vnc = hasVnc(status)
261
262    d = dict(user=username,
263             on=status,
264             has_vnc=has_vnc,
265             machine=machine,
266             hostname=state.environ.get('SERVER_NAME', 'localhost'),
267             authtoken=token)
268    return templates.vnc(searchList=[d])
269
270def getHostname(nic):
271    """Find the hostname associated with a NIC.
272
273    XXX this should be merged with the similar logic in DNS and DHCP.
274    """
275    if nic.hostname and '.' in nic.hostname:
276        return nic.hostname
277    elif nic.machine:
278        return nic.machine.name + '.xvm.mit.edu'
279    else:
280        return None
281
282
283def getNicInfo(data_dict, machine):
284    """Helper function for info, get data on nics for a machine.
285
286    Modifies data_dict to include the relevant data, and returns a list
287    of (key, name) pairs to display "name: data_dict[key]" to the user.
288    """
289    data_dict['num_nics'] = len(machine.nics)
290    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
291                           ('nic%s_mac', 'NIC %s MAC Addr'),
292                           ('nic%s_ip', 'NIC %s IP'),
293                           ]
294    nic_fields = []
295    for i in range(len(machine.nics)):
296        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
297        if not i:
298            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
299        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
300        data_dict['nic%s_ip' % i] = machine.nics[i].ip
301    if len(machine.nics) == 1:
302        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
303    return nic_fields
304
305def getDiskInfo(data_dict, machine):
306    """Helper function for info, get data on disks for a machine.
307
308    Modifies data_dict to include the relevant data, and returns a list
309    of (key, name) pairs to display "name: data_dict[key]" to the user.
310    """
311    data_dict['num_disks'] = len(machine.disks)
312    disk_fields_template = [('%s_size', '%s size')]
313    disk_fields = []
314    for disk in machine.disks:
315        name = disk.guest_device_name
316        disk_fields.extend([(x % name, y % name) for x, y in
317                            disk_fields_template])
318        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
319    return disk_fields
320
321def command(username, state, path, fields):
322    """Handler for running commands like boot and delete on a VM."""
323    back = fields.getfirst('back')
324    try:
325        d = controls.commandResult(username, state, fields)
326        if d['command'] == 'Delete VM':
327            back = 'list'
328    except InvalidInput, err:
329        if not back:
330            raise
331        print >> sys.stderr, err
332        result = err
333    else:
334        result = 'Success!'
335        if not back:
336            return templates.command(searchList=[d])
337    if back == 'list':
338        state.clear() #Changed global state
339        d = getListDict(username, state)
340        d['result'] = result
341        return templates.list(searchList=[d])
342    elif back == 'info':
343        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
344        return ({'Status': '303 See Other',
345                 'Location': 'info?machine_id=%d' % machine.machine_id},
346                "You shouldn't see this message.")
347    else:
348        raise InvalidInput('back', back, 'Not a known back page.')
349
350def modifyDict(username, state, fields):
351    """Modify a machine as specified by CGI arguments.
352
353    Return a list of local variables for modify.tmpl.
354    """
355    olddisk = {}
356    transaction = ctx.current.create_transaction()
357    try:
358        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
359        validate = validation.Validate(username, state, **kws)
360        machine = validate.machine
361        oldname = machine.name
362
363        if hasattr(validate, 'memory'):
364            machine.memory = validate.memory
365
366        if hasattr(validate, 'vmtype'):
367            machine.type = validate.vmtype
368
369        if hasattr(validate, 'disksize'):
370            disksize = validate.disksize
371            disk = machine.disks[0]
372            if disk.size != disksize:
373                olddisk[disk.guest_device_name] = disksize
374                disk.size = disksize
375                ctx.current.save(disk)
376
377        update_acl = False
378        if hasattr(validate, 'owner') and validate.owner != machine.owner:
379            machine.owner = validate.owner
380            update_acl = True
381        if hasattr(validate, 'name'):
382            machine.name = validate.name
383        if hasattr(validate, 'description'):
384            machine.description = validate.description
385        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
386            machine.administrator = validate.admin
387            update_acl = True
388        if hasattr(validate, 'contact'):
389            machine.contact = validate.contact
390
391        ctx.current.save(machine)
392        if update_acl:
393            print >> sys.stderr, machine, machine.administrator
394            cache_acls.refreshMachine(machine)
395        transaction.commit()
396    except:
397        transaction.rollback()
398        raise
399    for diskname in olddisk:
400        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
401    if hasattr(validate, 'name'):
402        controls.renameMachine(machine, oldname, validate.name)
403    return dict(user=username,
404                command="modify",
405                machine=machine)
406
407def modify(username, state, path, fields):
408    """Handler for modifying attributes of a machine."""
409    try:
410        modify_dict = modifyDict(username, state, fields)
411    except InvalidInput, err:
412        result = None
413        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
414    else:
415        machine = modify_dict['machine']
416        result = 'Success!'
417        err = None
418    info_dict = infoDict(username, state, machine)
419    info_dict['err'] = err
420    if err:
421        for field in fields.keys():
422            setattr(info_dict['defaults'], field, fields.getfirst(field))
423    info_dict['result'] = result
424    return templates.info(searchList=[info_dict])
425
426
427def helpHandler(username, state, path, fields):
428    """Handler for help messages."""
429    simple = fields.getfirst('simple')
430    subjects = fields.getlist('subject')
431
432    help_mapping = {'ParaVM Console': """
433ParaVM machines do not support local console access over VNC.  To
434access the serial console of these machines, you can SSH with Kerberos
435to console.xvm.mit.edu, using the name of the machine as your
436username.""",
437                    'HVM/ParaVM': """
438HVM machines use the virtualization features of the processor, while
439ParaVM machines use Xen's emulation of virtualization features.  You
440want an HVM virtualized machine.""",
441                    'CPU Weight': """
442Don't ask us!  We're as mystified as you are.""",
443                    'Owner': """
444The owner field is used to determine <a
445href="help?subject=Quotas">quotas</a>.  It must be the name of a
446locker that you are an AFS administrator of.  In particular, you or an
447AFS group you are a member of must have AFS rlidwka bits on the
448locker.  You can check who administers the LOCKER locker using the
449commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
450href="help?subject=Administrator">administrator</a>.""",
451                    'Administrator': """
452The administrator field determines who can access the console and
453power on and off the machine.  This can be either a user or a moira
454group.""",
455                    'Quotas': """
456Quotas are determined on a per-locker basis.  Each locker may have a
457maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
458active machines.""",
459                    'Console': """
460<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
461setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
462your machine will run just fine, but the applet's display of the
463console will suffer artifacts.
464"""
465                    }
466
467    if not subjects:
468        subjects = sorted(help_mapping.keys())
469
470    d = dict(user=username,
471             simple=simple,
472             subjects=subjects,
473             mapping=help_mapping)
474
475    return templates.help(searchList=[d])
476
477
478def badOperation(u, s, p, e):
479    """Function called when accessing an unknown URI."""
480    return ({'Status': '404 Not Found'}, 'Invalid operation.')
481
482def infoDict(username, state, machine):
483    """Get the variables used by info.tmpl."""
484    status = controls.statusInfo(machine)
485    checkpoint.checkpoint('Getting status info')
486    has_vnc = hasVnc(status)
487    if status is None:
488        main_status = dict(name=machine.name,
489                           memory=str(machine.memory))
490        uptime = None
491        cputime = None
492    else:
493        main_status = dict(status[1:])
494        start_time = float(main_status.get('start_time', 0))
495        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
496        cpu_time_float = float(main_status.get('cpu_time', 0))
497        cputime = datetime.timedelta(seconds=int(cpu_time_float))
498    checkpoint.checkpoint('Status')
499    display_fields = """name uptime memory state cpu_weight on_reboot
500     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
501    display_fields = [('name', 'Name'),
502                      ('description', 'Description'),
503                      ('owner', 'Owner'),
504                      ('administrator', 'Administrator'),
505                      ('contact', 'Contact'),
506                      ('type', 'Type'),
507                      'NIC_INFO',
508                      ('uptime', 'uptime'),
509                      ('cputime', 'CPU usage'),
510                      ('memory', 'RAM'),
511                      'DISK_INFO',
512                      ('state', 'state (xen format)'),
513                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
514                      ('on_reboot', 'Action on VM reboot'),
515                      ('on_poweroff', 'Action on VM poweroff'),
516                      ('on_crash', 'Action on VM crash'),
517                      ('on_xend_start', 'Action on Xen start'),
518                      ('on_xend_stop', 'Action on Xen stop'),
519                      ('bootloader', 'Bootloader options'),
520                      ]
521    fields = []
522    machine_info = {}
523    machine_info['name'] = machine.name
524    machine_info['description'] = machine.description
525    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
526    machine_info['owner'] = machine.owner
527    machine_info['administrator'] = machine.administrator
528    machine_info['contact'] = machine.contact
529
530    nic_fields = getNicInfo(machine_info, machine)
531    nic_point = display_fields.index('NIC_INFO')
532    display_fields = (display_fields[:nic_point] + nic_fields +
533                      display_fields[nic_point+1:])
534
535    disk_fields = getDiskInfo(machine_info, machine)
536    disk_point = display_fields.index('DISK_INFO')
537    display_fields = (display_fields[:disk_point] + disk_fields +
538                      display_fields[disk_point+1:])
539
540    main_status['memory'] += ' MiB'
541    for field, disp in display_fields:
542        if field in ('uptime', 'cputime') and locals()[field] is not None:
543            fields.append((disp, locals()[field]))
544        elif field in machine_info:
545            fields.append((disp, machine_info[field]))
546        elif field in main_status:
547            fields.append((disp, main_status[field]))
548        else:
549            pass
550            #fields.append((disp, None))
551
552    checkpoint.checkpoint('Got fields')
553
554
555    max_mem = validation.maxMemory(machine.owner, state, machine, False)
556    checkpoint.checkpoint('Got mem')
557    max_disk = validation.maxDisk(machine.owner, machine)
558    defaults = Defaults()
559    for name in 'machine_id name description administrator owner memory contact'.split():
560        setattr(defaults, name, getattr(machine, name))
561    defaults.type = machine.type.type_id
562    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
563    checkpoint.checkpoint('Got defaults')
564    d = dict(user=username,
565             on=status is not None,
566             machine=machine,
567             defaults=defaults,
568             has_vnc=has_vnc,
569             uptime=str(uptime),
570             ram=machine.memory,
571             max_mem=max_mem,
572             max_disk=max_disk,
573             owner_help=helppopup("Owner"),
574             fields = fields)
575    return d
576
577def info(username, state, path, fields):
578    """Handler for info on a single VM."""
579    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
580    d = infoDict(username, state, machine)
581    checkpoint.checkpoint('Got infodict')
582    return templates.info(searchList=[d])
583
584def unauthFront(_, _2, _3, fields):
585    """Information for unauth'd users."""
586    return templates.unauth(searchList=[{'simple' : True}])
587
588def overlord(username, state, path, fields):
589    if path == '':
590        return ({'Status': '303 See Other',
591                 'Location': 'overlord/'},
592                "You shouldn't see this message.")
593    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
594        raise InvalidInput('username', username, 'Not an overlord.')
595    newstate = State(username, overlord=True)
596    newstate.environ = state.environ
597    return handler(username, newstate, path, fields)
598
599def throwError(_, __, ___, ____):
600    """Throw an error, to test the error-tracing mechanisms."""
601    raise RuntimeError("test of the emergency broadcast system")
602
603mapping = dict(list=listVms,
604               vnc=vnc,
605               command=command,
606               modify=modify,
607               info=info,
608               create=create,
609               help=helpHandler,
610               unauth=unauthFront,
611               overlord=overlord,
612               errortest=throwError)
613
614def printHeaders(headers):
615    """Print a dictionary as HTTP headers."""
616    for key, value in headers.iteritems():
617        print '%s: %s' % (key, value)
618    print
619
620def send_error_mail(subject, body):
621    import subprocess
622
623    to = 'xvm@mit.edu'
624    mail = """To: %s
625From: root@xvm.mit.edu
626Subject: %s
627
628%s
629""" % (to, subject, body)
630    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
631    p.stdin.write(mail)
632    p.stdin.close()
633    p.wait()
634
635def show_error(op, username, fields, err, emsg, traceback):
636    """Print an error page when an exception occurs"""
637    d = dict(op=op, user=username, fields=fields,
638             errorMessage=str(err), stderr=emsg, traceback=traceback)
639    details = templates.error_raw(searchList=[d])
640    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
641        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
642                        details)
643    d['details'] = details
644    return templates.error(searchList=[d])
645
646def getUser(environ):
647    """Return the current user based on the SSL environment variables"""
648    return environ.get('REMOTE_USER', None)
649
650def handler(username, state, path, fields):
651    operation, path = pathSplit(path)
652    if not operation:
653        operation = 'list'
654    print 'Starting', operation
655    fun = mapping.get(operation, badOperation)
656    return fun(username, state, path, fields)
657
658class App:
659    def __init__(self, environ, start_response):
660        self.environ = environ
661        self.start = start_response
662
663        self.username = getUser(environ)
664        self.state = State(self.username)
665        self.state.environ = environ
666
667        random.seed() #sigh
668
669    def __iter__(self):
670        start_time = time.time()
671        sipb_xen_database.clear_cache()
672        sys.stderr = StringIO()
673        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
674        operation = self.environ.get('PATH_INFO', '')
675        if not operation:
676            self.start("301 Moved Permanently", [('Location', './')])
677            return
678        if self.username is None:
679            operation = 'unauth'
680
681        try:
682            checkpoint.checkpoint('Before')
683            output = handler(self.username, self.state, operation, fields)
684            checkpoint.checkpoint('After')
685
686            headers = dict(DEFAULT_HEADERS)
687            if isinstance(output, tuple):
688                new_headers, output = output
689                headers.update(new_headers)
690            e = revertStandardError()
691            if e:
692                if isinstance(output, basestring):
693                    sys.stderr = StringIO()
694                    x = str(output)
695                    print >> sys.stderr, x
696                    print >> sys.stderr, 'XXX'
697                    print >> sys.stderr, e
698                    raise Exception()
699                output.addError(e)
700            output_string =  str(output)
701            checkpoint.checkpoint('output as a string')
702        except Exception, err:
703            if not fields.has_key('js'):
704                if isinstance(err, InvalidInput):
705                    self.start('200 OK', [('Content-Type', 'text/html')])
706                    e = revertStandardError()
707                    yield str(invalidInput(operation, self.username, fields,
708                                           err, e))
709                    return
710            import traceback
711            self.start('500 Internal Server Error',
712                       [('Content-Type', 'text/html')])
713            e = revertStandardError()
714            s = show_error(operation, self.username, fields,
715                           err, e, traceback.format_exc())
716            yield str(s)
717            return
718        status = headers.setdefault('Status', '200 OK')
719        del headers['Status']
720        self.start(status, headers.items())
721        yield output_string
722        if fields.has_key('timedebug'):
723            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
724
725def constructor():
726    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
727    return App
728
729def main():
730    from flup.server.fcgi_fork import WSGIServer
731    WSGIServer(constructor()).run()
732
733if __name__ == '__main__':
734    main()
Note: See TracBrowser for help on using the repository browser.