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

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

Seed the random number generator after the fork.

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