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

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

collect all available data, in CodeError? case for now

  • Property svn:executable set to *
File size: 25.4 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
34sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
35
36import templates
37from Cheetah.Template import Template
38import sipb_xen_database
39from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
40import validation
41import cache_acls
42from webcommon import InvalidInput, CodeError, State
43import controls
44
45class Checkpoint:
46    def __init__(self):
47        self.start_time = time.time()
48        self.checkpoints = []
49
50    def checkpoint(self, s):
51        self.checkpoints.append((s, time.time()))
52
53    def __str__(self):
54        return ('Timing info:\n%s\n' %
55                '\n'.join(['%s: %s' % (d, t - self.start_time) for
56                           (d, t) in self.checkpoints]))
57
58checkpoint = Checkpoint()
59
60def jquote(string):
61    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
62
63def helppopup(subj):
64    """Return HTML code for a (?) link to a specified help topic"""
65    return ('<span class="helplink"><a href="help?' +
66            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
67            +'" target="_blank" ' +
68            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
69
70def makeErrorPre(old, addition):
71    if addition is None:
72        return
73    if old:
74        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
75    else:
76        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
77
78Template.sipb_xen_database = sipb_xen_database
79Template.helppopup = staticmethod(helppopup)
80Template.err = None
81
82class JsonDict:
83    """Class to store a dictionary that will be converted to JSON"""
84    def __init__(self, **kws):
85        self.data = kws
86        if 'err' in kws:
87            err = kws['err']
88            del kws['err']
89            self.addError(err)
90
91    def __str__(self):
92        return simplejson.dumps(self.data)
93
94    def addError(self, text):
95        """Add stderr text to be displayed on the website."""
96        self.data['err'] = \
97            makeErrorPre(self.data.get('err'), text)
98
99class Defaults:
100    """Class to store default values for fields."""
101    memory = 256
102    disk = 4.0
103    cdrom = ''
104    autoinstall = ''
105    name = ''
106    type = 'linux-hvm'
107
108    def __init__(self, max_memory=None, max_disk=None, **kws):
109        if max_memory is not None:
110            self.memory = min(self.memory, max_memory)
111        if max_disk is not None:
112            self.max_disk = min(self.disk, max_disk)
113        for key in kws:
114            setattr(self, key, kws[key])
115
116
117
118DEFAULT_HEADERS = {'Content-Type': 'text/html'}
119
120def error(op, username, fields, err, emsg, traceback):
121    """Print an error page when a CodeError occurs"""
122    send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
123                    'error on %s for %s: %s\n\n%s\n'
124                    % (op, username, err, emsg))
125    d = dict(op=op, user=username, fields=fields,
126             errorMessage=str(err), stderr=emsg, traceback=traceback)
127    return templates.error(searchList=[d])
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 owner memory disksize vmtype cdrom clone_from'.split()])
148    validate = validation.Validate(username, state, strict=True, **kws)
149    return dict(contact=username, name=validate.name, memory=validate.memory,
150                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
151                cdrom=getattr(validate, 'cdrom', None),
152                clone_from=getattr(validate, 'clone_from', None))
153
154def create(username, state, 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, 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, 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, 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 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, 'admin') and validate.admin != machine.administrator:
384            machine.administrator = validate.admin
385            update_acl = True
386        if hasattr(validate, 'contact'):
387            machine.contact = validate.contact
388
389        ctx.current.save(machine)
390        if update_acl:
391            print >> sys.stderr, machine, machine.administrator
392            cache_acls.refreshMachine(machine)
393        transaction.commit()
394    except:
395        transaction.rollback()
396        raise
397    for diskname in olddisk:
398        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
399    if hasattr(validate, 'name'):
400        controls.renameMachine(machine, oldname, validate.name)
401    return dict(user=username,
402                command="modify",
403                machine=machine)
404
405def modify(username, state, fields):
406    """Handler for modifying attributes of a machine."""
407    try:
408        modify_dict = modifyDict(username, state, fields)
409    except InvalidInput, err:
410        result = None
411        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
412    else:
413        machine = modify_dict['machine']
414        result = 'Success!'
415        err = None
416    info_dict = infoDict(username, state, machine)
417    info_dict['err'] = err
418    if err:
419        for field in fields.keys():
420            setattr(info_dict['defaults'], field, fields.getfirst(field))
421    info_dict['result'] = result
422    return templates.info(searchList=[info_dict])
423
424
425def helpHandler(username, state, fields):
426    """Handler for help messages."""
427    simple = fields.getfirst('simple')
428    subjects = fields.getlist('subject')
429
430    help_mapping = {'ParaVM Console': """
431ParaVM machines do not support local console access over VNC.  To
432access the serial console of these machines, you can SSH with Kerberos
433to console.xvm.mit.edu, using the name of the machine as your
434username.""",
435                    'HVM/ParaVM': """
436HVM machines use the virtualization features of the processor, while
437ParaVM machines use Xen's emulation of virtualization features.  You
438want an HVM virtualized machine.""",
439                    'CPU Weight': """
440Don't ask us!  We're as mystified as you are.""",
441                    'Owner': """
442The owner field is used to determine <a
443href="help?subject=Quotas">quotas</a>.  It must be the name of a
444locker that you are an AFS administrator of.  In particular, you or an
445AFS group you are a member of must have AFS rlidwka bits on the
446locker.  You can check who administers the LOCKER locker using the
447commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
448href="help?subject=Administrator">administrator</a>.""",
449                    'Administrator': """
450The administrator field determines who can access the console and
451power on and off the machine.  This can be either a user or a moira
452group.""",
453                    'Quotas': """
454Quotas are determined on a per-locker basis.  Each locker may have a
455maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
456active machines.""",
457                    'Console': """
458<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
459setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
460your machine will run just fine, but the applet's display of the
461console will suffer artifacts.
462"""
463                    }
464
465    if not subjects:
466        subjects = sorted(help_mapping.keys())
467
468    d = dict(user=username,
469             simple=simple,
470             subjects=subjects,
471             mapping=help_mapping)
472
473    return templates.help(searchList=[d])
474
475
476def badOperation(u, s, e):
477    """Function called when accessing an unknown URI."""
478    raise CodeError("Unknown operation")
479
480def infoDict(username, state, machine):
481    """Get the variables used by info.tmpl."""
482    status = controls.statusInfo(machine)
483    checkpoint.checkpoint('Getting status info')
484    has_vnc = hasVnc(status)
485    if status is None:
486        main_status = dict(name=machine.name,
487                           memory=str(machine.memory))
488        uptime = None
489        cputime = None
490    else:
491        main_status = dict(status[1:])
492        start_time = float(main_status.get('start_time', 0))
493        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
494        cpu_time_float = float(main_status.get('cpu_time', 0))
495        cputime = datetime.timedelta(seconds=int(cpu_time_float))
496    checkpoint.checkpoint('Status')
497    display_fields = """name uptime memory state cpu_weight on_reboot
498     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
499    display_fields = [('name', 'Name'),
500                      ('owner', 'Owner'),
501                      ('administrator', 'Administrator'),
502                      ('contact', 'Contact'),
503                      ('type', 'Type'),
504                      'NIC_INFO',
505                      ('uptime', 'uptime'),
506                      ('cputime', 'CPU usage'),
507                      ('memory', 'RAM'),
508                      'DISK_INFO',
509                      ('state', 'state (xen format)'),
510                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
511                      ('on_reboot', 'Action on VM reboot'),
512                      ('on_poweroff', 'Action on VM poweroff'),
513                      ('on_crash', 'Action on VM crash'),
514                      ('on_xend_start', 'Action on Xen start'),
515                      ('on_xend_stop', 'Action on Xen stop'),
516                      ('bootloader', 'Bootloader options'),
517                      ]
518    fields = []
519    machine_info = {}
520    machine_info['name'] = machine.name
521    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
522    machine_info['owner'] = machine.owner
523    machine_info['administrator'] = machine.administrator
524    machine_info['contact'] = machine.contact
525
526    nic_fields = getNicInfo(machine_info, machine)
527    nic_point = display_fields.index('NIC_INFO')
528    display_fields = (display_fields[:nic_point] + nic_fields +
529                      display_fields[nic_point+1:])
530
531    disk_fields = getDiskInfo(machine_info, machine)
532    disk_point = display_fields.index('DISK_INFO')
533    display_fields = (display_fields[:disk_point] + disk_fields +
534                      display_fields[disk_point+1:])
535
536    main_status['memory'] += ' MiB'
537    for field, disp in display_fields:
538        if field in ('uptime', 'cputime') and locals()[field] is not None:
539            fields.append((disp, locals()[field]))
540        elif field in machine_info:
541            fields.append((disp, machine_info[field]))
542        elif field in main_status:
543            fields.append((disp, main_status[field]))
544        else:
545            pass
546            #fields.append((disp, None))
547
548    checkpoint.checkpoint('Got fields')
549
550
551    max_mem = validation.maxMemory(machine.owner, state, machine, False)
552    checkpoint.checkpoint('Got mem')
553    max_disk = validation.maxDisk(machine.owner, machine)
554    defaults = Defaults()
555    for name in 'machine_id name administrator owner memory contact'.split():
556        setattr(defaults, name, getattr(machine, name))
557    defaults.type = machine.type.type_id
558    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
559    checkpoint.checkpoint('Got defaults')
560    d = dict(user=username,
561             on=status is not None,
562             machine=machine,
563             defaults=defaults,
564             has_vnc=has_vnc,
565             uptime=str(uptime),
566             ram=machine.memory,
567             max_mem=max_mem,
568             max_disk=max_disk,
569             owner_help=helppopup("Owner"),
570             fields = fields)
571    return d
572
573def info(username, state, fields):
574    """Handler for info on a single VM."""
575    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
576    d = infoDict(username, state, machine)
577    checkpoint.checkpoint('Got infodict')
578    return templates.info(searchList=[d])
579
580def unauthFront(_, _2, fields):
581    """Information for unauth'd users."""
582    return templates.unauth(searchList=[{'simple' : True}])
583
584def throwError(_, __, ___):
585    """Throw an error, to test the error-tracing mechanisms."""
586    raise CodeError("test of the emergency broadcast system")
587
588mapping = dict(list=listVms,
589               vnc=vnc,
590               command=command,
591               modify=modify,
592               info=info,
593               create=create,
594               help=helpHandler,
595               unauth=unauthFront,
596               errortest=throwError)
597
598def printHeaders(headers):
599    """Print a dictionary as HTTP headers."""
600    for key, value in headers.iteritems():
601        print '%s: %s' % (key, value)
602    print
603
604def send_error_mail(subject, body):
605    import subprocess
606
607    to = 'xvm@mit.edu'
608    mail = """To: %s
609From: root@xvm.mit.edu
610Subject: %s
611
612%s
613""" % (to, subject, body)
614    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
615    p.stdin.write(mail)
616    p.stdin.close()
617    p.wait()
618
619def getUser(environ):
620    """Return the current user based on the SSL environment variables"""
621    email = environ.get('SSL_CLIENT_S_DN_Email', None)
622    if email is None:
623        return None
624    if not email.endswith('@MIT.EDU'):
625        return None
626    return email[:-8]
627
628class App:
629    def __init__(self, environ, start_response):
630        self.environ = environ
631        self.start = start_response
632
633        self.username = getUser(environ)
634        self.state = State(self.username)
635        self.state.environ = environ
636
637    def __iter__(self):
638        sys.stderr = StringIO()
639        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
640        operation = self.environ.get('PATH_INFO', '')
641        if not operation:
642            self.start("301 Moved Permanently", [('Location',
643                                                  self.environ['SCRIPT_NAME']+'/')])
644            return
645        if self.username is None:
646            operation = 'unauth'
647        if operation.startswith('/'):
648            operation = operation[1:]
649        if not operation:
650            operation = 'list'
651        print 'Starting', operation
652
653        start_time = time.time()
654        fun = mapping.get(operation, badOperation)
655        try:
656            checkpoint.checkpoint('Before')
657            output = fun(self.username, self.state, fields)
658            checkpoint.checkpoint('After')
659
660            headers = dict(DEFAULT_HEADERS)
661            if isinstance(output, tuple):
662                new_headers, output = output
663                headers.update(new_headers)
664            print 'MOO2'
665            e = revertStandardError()
666            if e:
667                if isinstance(output, basestring):
668                    sys.stderr = StringIO()
669                    x = str(output)
670                    print >> sys.stderr, x
671                    print >> sys.stderr, 'XXX'
672                    print >> sys.stderr, e
673                    raise Exception()
674                output.addError(e)
675            output_string =  str(output)
676            checkpoint.checkpoint('output as a string')
677        except Exception, err:
678            import traceback
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,
684                              err, e, traceback.format_exc())
685                    yield str(s)
686                    return
687                if isinstance(err, InvalidInput):
688                    self.start('200 OK', [('Content-Type', 'text/html')])
689                    e = revertStandardError()
690                    yield str(invalidInput(operation, self.username, fields, err, e))
691                    return
692            self.start('500 Internal Server Error', [('Content-Type', 'text/plain')])
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.