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

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

send mail on all exceptions

  • Property svn:executable set to *
File size: 25.3 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    sys.stderr = StringIO()
34
35sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
36
37import templates
38from Cheetah.Template import Template
39import sipb_xen_database
40from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
41import validation
42import cache_acls
43from webcommon import InvalidInput, CodeError, State
44import controls
45
46class Checkpoint:
47    def __init__(self):
48        self.start_time = time.time()
49        self.checkpoints = []
50
51    def checkpoint(self, s):
52        self.checkpoints.append((s, time.time()))
53
54    def __str__(self):
55        return ('Timing info:\n%s\n' %
56                '\n'.join(['%s: %s' % (d, t - self.start_time) for
57                           (d, t) in self.checkpoints]))
58
59checkpoint = Checkpoint()
60
61def jquote(string):
62    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
63
64def helppopup(subj):
65    """Return HTML code for a (?) link to a specified help topic"""
66    return ('<span class="helplink"><a href="help?' +
67            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
68            +'" target="_blank" ' +
69            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
70
71def makeErrorPre(old, addition):
72    if addition is None:
73        return
74    if old:
75        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
76    else:
77        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
78
79Template.sipb_xen_database = sipb_xen_database
80Template.helppopup = staticmethod(helppopup)
81Template.err = None
82
83class JsonDict:
84    """Class to store a dictionary that will be converted to JSON"""
85    def __init__(self, **kws):
86        self.data = kws
87        if 'err' in kws:
88            err = kws['err']
89            del kws['err']
90            self.addError(err)
91
92    def __str__(self):
93        return simplejson.dumps(self.data)
94
95    def addError(self, text):
96        """Add stderr text to be displayed on the website."""
97        self.data['err'] = \
98            makeErrorPre(self.data.get('err'), text)
99
100class Defaults:
101    """Class to store default values for fields."""
102    memory = 256
103    disk = 4.0
104    cdrom = ''
105    autoinstall = ''
106    name = ''
107    type = 'linux-hvm'
108
109    def __init__(self, max_memory=None, max_disk=None, **kws):
110        if max_memory is not None:
111            self.memory = min(self.memory, max_memory)
112        if max_disk is not None:
113            self.max_disk = min(self.disk, max_disk)
114        for key in kws:
115            setattr(self, key, kws[key])
116
117
118
119DEFAULT_HEADERS = {'Content-Type': 'text/html'}
120
121def error(op, username, fields, err, emsg):
122    """Print an error page when a CodeError occurs"""
123    send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
124                    'error on %s for %s: %s\n\n%s\n'
125                    % (op, username, err, emsg))
126    d = dict(op=op, user=username, errorMessage=str(err),
127             stderr=emsg)
128    return templates.error(searchList=[d])
129
130def invalidInput(op, username, fields, err, emsg):
131    """Print an error page when an InvalidInput exception occurs"""
132    d = dict(op=op, user=username, err_field=err.err_field,
133             err_value=str(err.err_value), stderr=emsg,
134             errorMessage=str(err))
135    return templates.invalid(searchList=[d])
136
137def hasVnc(status):
138    """Does the machine with a given status list support VNC?"""
139    if status is None:
140        return False
141    for l in status:
142        if l[0] == 'device' and l[1][0] == 'vfb':
143            d = dict(l[1][1:])
144            return 'location' in d
145    return False
146
147def parseCreate(username, state, fields):
148    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name owner memory disksize vmtype cdrom clone_from'.split()])
149    validate = validation.Validate(username, state, strict=True, **kws)
150    return dict(contact=username, name=validate.name, memory=validate.memory,
151                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
152                cdrom=getattr(validate, 'cdrom', None),
153                clone_from=getattr(validate, 'clone_from', None))
154
155def create(username, state, fields):
156    """Handler for create requests."""
157    try:
158        parsed_fields = parseCreate(username, state, fields)
159        machine = controls.createVm(username, state, **parsed_fields)
160    except InvalidInput, err:
161        pass
162    else:
163        err = None
164    state.clear() #Changed global state
165    d = getListDict(username, state)
166    d['err'] = err
167    if err:
168        for field in fields.keys():
169            setattr(d['defaults'], field, fields.getfirst(field))
170    else:
171        d['new_machine'] = parsed_fields['name']
172    return templates.list(searchList=[d])
173
174
175def getListDict(username, state):
176    """Gets the list of local variables used by list.tmpl."""
177    checkpoint.checkpoint('Starting')
178    machines = state.machines
179    checkpoint.checkpoint('Got my machines')
180    on = {}
181    has_vnc = {}
182    xmlist = state.xmlist
183    checkpoint.checkpoint('Got uptimes')
184    can_clone = 'ice3' not in state.xmlist_raw
185    for m in machines:
186        if m not in xmlist:
187            has_vnc[m] = 'Off'
188            m.uptime = None
189        else:
190            m.uptime = xmlist[m]['uptime']
191            if xmlist[m]['console']:
192                has_vnc[m] = True
193            elif m.type.hvm:
194                has_vnc[m] = "WTF?"
195            else:
196                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
197    max_memory = validation.maxMemory(username, state)
198    max_disk = validation.maxDisk(username)
199    checkpoint.checkpoint('Got max mem/disk')
200    defaults = Defaults(max_memory=max_memory,
201                        max_disk=max_disk,
202                        owner=username,
203                        cdrom='gutsy-i386')
204    checkpoint.checkpoint('Got defaults')
205    def sortkey(machine):
206        return (machine.owner != username, machine.owner, machine.name)
207    machines = sorted(machines, key=sortkey)
208    d = dict(user=username,
209             cant_add_vm=validation.cantAddVm(username, state),
210             max_memory=max_memory,
211             max_disk=max_disk,
212             defaults=defaults,
213             machines=machines,
214             has_vnc=has_vnc,
215             can_clone=can_clone)
216    return d
217
218def listVms(username, state, fields):
219    """Handler for list requests."""
220    checkpoint.checkpoint('Getting list dict')
221    d = getListDict(username, state)
222    checkpoint.checkpoint('Got list dict')
223    return templates.list(searchList=[d])
224
225def vnc(username, state, fields):
226    """VNC applet page.
227
228    Note that due to same-domain restrictions, the applet connects to
229    the webserver, which needs to forward those requests to the xen
230    server.  The Xen server runs another proxy that (1) authenticates
231    and (2) finds the correct port for the VM.
232
233    You might want iptables like:
234
235    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
236      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
237    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
238      --dport 10003 -j SNAT --to-source 18.187.7.142
239    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
240      --dport 10003 -j ACCEPT
241
242    Remember to enable iptables!
243    echo 1 > /proc/sys/net/ipv4/ip_forward
244    """
245    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
246
247    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
248
249    data = {}
250    data["user"] = username
251    data["machine"] = machine.name
252    data["expires"] = time.time()+(5*60)
253    pickled_data = cPickle.dumps(data)
254    m = hmac.new(TOKEN_KEY, digestmod=sha)
255    m.update(pickled_data)
256    token = {'data': pickled_data, 'digest': m.digest()}
257    token = cPickle.dumps(token)
258    token = base64.urlsafe_b64encode(token)
259
260    status = controls.statusInfo(machine)
261    has_vnc = hasVnc(status)
262
263    d = dict(user=username,
264             on=status,
265             has_vnc=has_vnc,
266             machine=machine,
267             hostname=state.environ.get('SERVER_NAME', 'localhost'),
268             authtoken=token)
269    return templates.vnc(searchList=[d])
270
271def getHostname(nic):
272    """Find the hostname associated with a NIC.
273
274    XXX this should be merged with the similar logic in DNS and DHCP.
275    """
276    if nic.hostname and '.' in nic.hostname:
277        return nic.hostname
278    elif nic.machine:
279        return nic.machine.name + '.xvm.mit.edu'
280    else:
281        return None
282
283
284def getNicInfo(data_dict, machine):
285    """Helper function for info, get data on nics for a machine.
286
287    Modifies data_dict to include the relevant data, and returns a list
288    of (key, name) pairs to display "name: data_dict[key]" to the user.
289    """
290    data_dict['num_nics'] = len(machine.nics)
291    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
292                           ('nic%s_mac', 'NIC %s MAC Addr'),
293                           ('nic%s_ip', 'NIC %s IP'),
294                           ]
295    nic_fields = []
296    for i in range(len(machine.nics)):
297        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
298        if not i:
299            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
300        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
301        data_dict['nic%s_ip' % i] = machine.nics[i].ip
302    if len(machine.nics) == 1:
303        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
304    return nic_fields
305
306def getDiskInfo(data_dict, machine):
307    """Helper function for info, get data on disks for a machine.
308
309    Modifies data_dict to include the relevant data, and returns a list
310    of (key, name) pairs to display "name: data_dict[key]" to the user.
311    """
312    data_dict['num_disks'] = len(machine.disks)
313    disk_fields_template = [('%s_size', '%s size')]
314    disk_fields = []
315    for disk in machine.disks:
316        name = disk.guest_device_name
317        disk_fields.extend([(x % name, y % name) for x, y in
318                            disk_fields_template])
319        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
320    return disk_fields
321
322def command(username, state, fields):
323    """Handler for running commands like boot and delete on a VM."""
324    back = fields.getfirst('back')
325    try:
326        d = controls.commandResult(username, state, fields)
327        if d['command'] == 'Delete VM':
328            back = 'list'
329    except InvalidInput, err:
330        if not back:
331            raise
332        print >> sys.stderr, err
333        result = err
334    else:
335        result = 'Success!'
336        if not back:
337            return templates.command(searchList=[d])
338    if back == 'list':
339        state.clear() #Changed global state
340        d = getListDict(username, state)
341        d['result'] = result
342        return templates.list(searchList=[d])
343    elif back == 'info':
344        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
345        return ({'Status': '303 See Other',
346                 'Location': '/info?machine_id=%d' % machine.machine_id},
347                "You shouldn't see this message.")
348    else:
349        raise InvalidInput('back', back, 'Not a known back page.')
350
351def modifyDict(username, state, fields):
352    """Modify a machine as specified by CGI arguments.
353
354    Return a list of local variables for modify.tmpl.
355    """
356    olddisk = {}
357    transaction = ctx.current.create_transaction()
358    try:
359        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name memory vmtype disksize'.split()])
360        validate = validation.Validate(username, state, **kws)
361        machine = validate.machine
362        oldname = machine.name
363
364        if hasattr(validate, 'memory'):
365            machine.memory = validate.memory
366
367        if hasattr(validate, 'vmtype'):
368            machine.type = validate.vmtype
369
370        if hasattr(validate, 'disksize'):
371            disksize = validate.disksize
372            disk = machine.disks[0]
373            if disk.size != disksize:
374                olddisk[disk.guest_device_name] = disksize
375                disk.size = disksize
376                ctx.current.save(disk)
377
378        update_acl = False
379        if hasattr(validate, 'owner') and validate.owner != machine.owner:
380            machine.owner = validate.owner
381            update_acl = True
382        if hasattr(validate, 'name'):
383            machine.name = validate.name
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, 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, 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, e):
478    """Function called when accessing an unknown URI."""
479    raise CodeError("Unknown 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                      ('owner', 'Owner'),
502                      ('administrator', 'Administrator'),
503                      ('contact', 'Contact'),
504                      ('type', 'Type'),
505                      'NIC_INFO',
506                      ('uptime', 'uptime'),
507                      ('cputime', 'CPU usage'),
508                      ('memory', 'RAM'),
509                      'DISK_INFO',
510                      ('state', 'state (xen format)'),
511                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
512                      ('on_reboot', 'Action on VM reboot'),
513                      ('on_poweroff', 'Action on VM poweroff'),
514                      ('on_crash', 'Action on VM crash'),
515                      ('on_xend_start', 'Action on Xen start'),
516                      ('on_xend_stop', 'Action on Xen stop'),
517                      ('bootloader', 'Bootloader options'),
518                      ]
519    fields = []
520    machine_info = {}
521    machine_info['name'] = machine.name
522    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
523    machine_info['owner'] = machine.owner
524    machine_info['administrator'] = machine.administrator
525    machine_info['contact'] = machine.contact
526
527    nic_fields = getNicInfo(machine_info, machine)
528    nic_point = display_fields.index('NIC_INFO')
529    display_fields = (display_fields[:nic_point] + nic_fields +
530                      display_fields[nic_point+1:])
531
532    disk_fields = getDiskInfo(machine_info, machine)
533    disk_point = display_fields.index('DISK_INFO')
534    display_fields = (display_fields[:disk_point] + disk_fields +
535                      display_fields[disk_point+1:])
536
537    main_status['memory'] += ' MiB'
538    for field, disp in display_fields:
539        if field in ('uptime', 'cputime') and locals()[field] is not None:
540            fields.append((disp, locals()[field]))
541        elif field in machine_info:
542            fields.append((disp, machine_info[field]))
543        elif field in main_status:
544            fields.append((disp, main_status[field]))
545        else:
546            pass
547            #fields.append((disp, None))
548
549    checkpoint.checkpoint('Got fields')
550
551
552    max_mem = validation.maxMemory(machine.owner, state, machine, False)
553    checkpoint.checkpoint('Got mem')
554    max_disk = validation.maxDisk(machine.owner, machine)
555    defaults = Defaults()
556    for name in 'machine_id name administrator owner memory contact'.split():
557        setattr(defaults, name, getattr(machine, name))
558    defaults.type = machine.type.type_id
559    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
560    checkpoint.checkpoint('Got defaults')
561    d = dict(user=username,
562             on=status is not None,
563             machine=machine,
564             defaults=defaults,
565             has_vnc=has_vnc,
566             uptime=str(uptime),
567             ram=machine.memory,
568             max_mem=max_mem,
569             max_disk=max_disk,
570             owner_help=helppopup("Owner"),
571             fields = fields)
572    return d
573
574def info(username, state, fields):
575    """Handler for info on a single VM."""
576    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
577    d = infoDict(username, state, machine)
578    checkpoint.checkpoint('Got infodict')
579    return templates.info(searchList=[d])
580
581def unauthFront(_, _2, fields):
582    """Information for unauth'd users."""
583    return templates.unauth(searchList=[{'simple' : True}])
584
585def throwError(_, __, ___):
586    """Throw an error, to test the error-tracing mechanisms."""
587    raise CodeError("test of the emergency broadcast system")
588
589mapping = dict(list=listVms,
590               vnc=vnc,
591               command=command,
592               modify=modify,
593               info=info,
594               create=create,
595               help=helpHandler,
596               unauth=unauthFront,
597               errortest=throwError)
598
599def printHeaders(headers):
600    """Print a dictionary as HTTP headers."""
601    for key, value in headers.iteritems():
602        print '%s: %s' % (key, value)
603    print
604
605def send_error_mail(subject, body):
606    import subprocess
607
608    to = 'xvm@mit.edu'
609    mail = """To: %s
610From: root@xvm.mit.edu
611Subject: %s
612
613%s
614""" % (to, subject, body)
615    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
616    p.stdin.write(mail)
617    p.stdin.close()
618    p.wait()
619
620def getUser(environ):
621    """Return the current user based on the SSL environment variables"""
622    email = environ.get('SSL_CLIENT_S_DN_Email', None)
623    if email is None:
624        return None
625    if not email.endswith('@MIT.EDU'):
626        return None
627    return email[:-8]
628
629class App:
630    def __init__(self, environ, start_response):
631        self.environ = environ
632        self.start = start_response
633
634        self.username = getUser(environ)
635        self.state = State(self.username)
636        self.state.environ = environ
637
638    def __iter__(self):
639        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
640        print >> sys.stderr, fields
641        operation = self.environ.get('PATH_INFO', '')
642        if not operation:
643            self.start("301 Moved Permanently", [('Location',
644                                                  self.environ['SCRIPT_NAME']+'/')])
645            return
646        if self.username is None:
647            operation = 'unauth'
648        if operation.startswith('/'):
649            operation = operation[1:]
650        if not operation:
651            operation = 'list'
652        print 'Starting', operation
653
654        start_time = time.time()
655        fun = mapping.get(operation, badOperation)
656        try:
657            checkpoint.checkpoint('Before')
658            output = fun(self.username, self.state, fields)
659            checkpoint.checkpoint('After')
660
661            headers = dict(DEFAULT_HEADERS)
662            if isinstance(output, tuple):
663                new_headers, output = output
664                headers.update(new_headers)
665            print 'MOO2'
666            e = revertStandardError()
667            if e:
668                if isinstance(output, basestring):
669                    sys.stderr = StringIO()
670                    x = str(output)
671                    print >> sys.stderr, x
672                    print >> sys.stderr, 'XXX'
673                    print >> sys.stderr, e
674                    raise Exception()
675                output.addError(e)
676            output_string =  str(output)
677            checkpoint.checkpoint('output as a string')
678        except Exception, err:
679            if not fields.has_key('js'):
680                if isinstance(err, CodeError):
681                    self.start('500 Internal Server Error', [('Content-Type', 'text/html')])
682                    e = revertStandardError()
683                    s = error(operation, self.username, fields, err, e)
684                    yield str(s)
685                    return
686                if isinstance(err, InvalidInput):
687                    self.start('200 OK', [('Content-Type', 'text/html')])
688                    e = revertStandardError()
689                    yield str(invalidInput(operation, self.username, fields, err, e))
690                    return
691            self.start('500 Internal Server Error', [('Content-Type', 'text/plain')])
692            import traceback
693            send_error_mail('xvm error: %s' % (err,),
694                            '%s\n' % (traceback.format_exc(),))
695            yield '''Uh-oh!  We experienced an error.
696Sorry about that.  We've gotten mail about it.
697
698Feel free to poke us at xvm@mit.edu if this bug is
699consistently biting you and we don't seem to be fixing it.
700
701In case you're curious, the gory details are here.
702----
703%s
704----
705%s
706----''' % (str(err), traceback.format_exc())
707        status = headers.setdefault('Status', '200 OK')
708        del headers['Status']
709        self.start(status, headers.items())
710        yield output_string
711        if fields.has_key('timedebug'):
712            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
713
714def constructor():
715    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
716    return App
717
718def main():
719    from flup.server.fcgi_fork import WSGIServer
720    WSGIServer(constructor()).run()
721
722if __name__ == '__main__':
723    main()
Note: See TracBrowser for help on using the repository browser.