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

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

Autoinstalls

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