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

Last change on this file since 664 was 662, checked in by price, 16 years ago

web: say where a VM is running

  • Property svn:executable set to *
File size: 25.9 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        main_status['host'] = controls.listHost(machine)
495        start_time = float(main_status.get('start_time', 0))
496        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
497        cpu_time_float = float(main_status.get('cpu_time', 0))
498        cputime = datetime.timedelta(seconds=int(cpu_time_float))
499    checkpoint.checkpoint('Status')
500    display_fields = """name uptime memory state cpu_weight on_reboot
501     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
502    display_fields = [('name', 'Name'),
503                      ('description', 'Description'),
504                      ('owner', 'Owner'),
505                      ('administrator', 'Administrator'),
506                      ('contact', 'Contact'),
507                      ('type', 'Type'),
508                      'NIC_INFO',
509                      ('uptime', 'uptime'),
510                      ('cputime', 'CPU usage'),
511                      ('host', 'Hosted on'),
512                      ('memory', 'RAM'),
513                      'DISK_INFO',
514                      ('state', 'state (xen format)'),
515                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
516                      ('on_reboot', 'Action on VM reboot'),
517                      ('on_poweroff', 'Action on VM poweroff'),
518                      ('on_crash', 'Action on VM crash'),
519                      ('on_xend_start', 'Action on Xen start'),
520                      ('on_xend_stop', 'Action on Xen stop'),
521                      ('bootloader', 'Bootloader options'),
522                      ]
523    fields = []
524    machine_info = {}
525    machine_info['name'] = machine.name
526    machine_info['description'] = machine.description
527    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
528    machine_info['owner'] = machine.owner
529    machine_info['administrator'] = machine.administrator
530    machine_info['contact'] = machine.contact
531
532    nic_fields = getNicInfo(machine_info, machine)
533    nic_point = display_fields.index('NIC_INFO')
534    display_fields = (display_fields[:nic_point] + nic_fields +
535                      display_fields[nic_point+1:])
536
537    disk_fields = getDiskInfo(machine_info, machine)
538    disk_point = display_fields.index('DISK_INFO')
539    display_fields = (display_fields[:disk_point] + disk_fields +
540                      display_fields[disk_point+1:])
541
542    main_status['memory'] += ' MiB'
543    for field, disp in display_fields:
544        if field in ('uptime', 'cputime') and locals()[field] is not None:
545            fields.append((disp, locals()[field]))
546        elif field in machine_info:
547            fields.append((disp, machine_info[field]))
548        elif field in main_status:
549            fields.append((disp, main_status[field]))
550        else:
551            pass
552            #fields.append((disp, None))
553
554    checkpoint.checkpoint('Got fields')
555
556
557    max_mem = validation.maxMemory(machine.owner, state, machine, False)
558    checkpoint.checkpoint('Got mem')
559    max_disk = validation.maxDisk(machine.owner, machine)
560    defaults = Defaults()
561    for name in 'machine_id name description administrator owner memory contact'.split():
562        setattr(defaults, name, getattr(machine, name))
563    defaults.type = machine.type.type_id
564    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
565    checkpoint.checkpoint('Got defaults')
566    d = dict(user=username,
567             on=status is not None,
568             machine=machine,
569             defaults=defaults,
570             has_vnc=has_vnc,
571             uptime=str(uptime),
572             ram=machine.memory,
573             max_mem=max_mem,
574             max_disk=max_disk,
575             owner_help=helppopup("Owner"),
576             fields = fields)
577    return d
578
579def info(username, state, path, fields):
580    """Handler for info on a single VM."""
581    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
582    d = infoDict(username, state, machine)
583    checkpoint.checkpoint('Got infodict')
584    return templates.info(searchList=[d])
585
586def unauthFront(_, _2, _3, fields):
587    """Information for unauth'd users."""
588    return templates.unauth(searchList=[{'simple' : True}])
589
590def overlord(username, state, path, fields):
591    if path == '':
592        return ({'Status': '303 See Other',
593                 'Location': 'overlord/'},
594                "You shouldn't see this message.")
595    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
596        raise InvalidInput('username', username, 'Not an overlord.')
597    newstate = State(username, overlord=True)
598    newstate.environ = state.environ
599    return handler(username, newstate, path, fields)
600
601def throwError(_, __, ___, ____):
602    """Throw an error, to test the error-tracing mechanisms."""
603    raise RuntimeError("test of the emergency broadcast system")
604
605mapping = dict(list=listVms,
606               vnc=vnc,
607               command=command,
608               modify=modify,
609               info=info,
610               create=create,
611               help=helpHandler,
612               unauth=unauthFront,
613               overlord=overlord,
614               errortest=throwError)
615
616def printHeaders(headers):
617    """Print a dictionary as HTTP headers."""
618    for key, value in headers.iteritems():
619        print '%s: %s' % (key, value)
620    print
621
622def send_error_mail(subject, body):
623    import subprocess
624
625    to = 'xvm@mit.edu'
626    mail = """To: %s
627From: root@xvm.mit.edu
628Subject: %s
629
630%s
631""" % (to, subject, body)
632    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
633    p.stdin.write(mail)
634    p.stdin.close()
635    p.wait()
636
637def show_error(op, username, fields, err, emsg, traceback):
638    """Print an error page when an exception occurs"""
639    d = dict(op=op, user=username, fields=fields,
640             errorMessage=str(err), stderr=emsg, traceback=traceback)
641    details = templates.error_raw(searchList=[d])
642    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
643        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
644                        details)
645    d['details'] = details
646    return templates.error(searchList=[d])
647
648def getUser(environ):
649    """Return the current user based on the SSL environment variables"""
650    return environ.get('REMOTE_USER', None)
651
652def handler(username, state, path, fields):
653    operation, path = pathSplit(path)
654    if not operation:
655        operation = 'list'
656    print 'Starting', operation
657    fun = mapping.get(operation, badOperation)
658    return fun(username, state, path, fields)
659
660class App:
661    def __init__(self, environ, start_response):
662        self.environ = environ
663        self.start = start_response
664
665        self.username = getUser(environ)
666        self.state = State(self.username)
667        self.state.environ = environ
668
669        random.seed() #sigh
670
671    def __iter__(self):
672        start_time = time.time()
673        sipb_xen_database.clear_cache()
674        sys.stderr = StringIO()
675        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
676        operation = self.environ.get('PATH_INFO', '')
677        if not operation:
678            self.start("301 Moved Permanently", [('Location', './')])
679            return
680        if self.username is None:
681            operation = 'unauth'
682
683        try:
684            checkpoint.checkpoint('Before')
685            output = handler(self.username, self.state, operation, fields)
686            checkpoint.checkpoint('After')
687
688            headers = dict(DEFAULT_HEADERS)
689            if isinstance(output, tuple):
690                new_headers, output = output
691                headers.update(new_headers)
692            e = revertStandardError()
693            if e:
694                if isinstance(output, basestring):
695                    sys.stderr = StringIO()
696                    x = str(output)
697                    print >> sys.stderr, x
698                    print >> sys.stderr, 'XXX'
699                    print >> sys.stderr, e
700                    raise Exception()
701                output.addError(e)
702            output_string =  str(output)
703            checkpoint.checkpoint('output as a string')
704        except Exception, err:
705            if not fields.has_key('js'):
706                if isinstance(err, InvalidInput):
707                    self.start('200 OK', [('Content-Type', 'text/html')])
708                    e = revertStandardError()
709                    yield str(invalidInput(operation, self.username, fields,
710                                           err, e))
711                    return
712            import traceback
713            self.start('500 Internal Server Error',
714                       [('Content-Type', 'text/html')])
715            e = revertStandardError()
716            s = show_error(operation, self.username, fields,
717                           err, e, traceback.format_exc())
718            yield str(s)
719            return
720        status = headers.setdefault('Status', '200 OK')
721        del headers['Status']
722        self.start(status, headers.items())
723        yield output_string
724        if fields.has_key('timedebug'):
725            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
726
727def constructor():
728    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
729    return App
730
731def main():
732    from flup.server.fcgi_fork import WSGIServer
733    WSGIServer(constructor()).run()
734
735if __name__ == '__main__':
736    main()
Note: See TracBrowser for help on using the repository browser.