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

Last change on this file since 610 was 609, checked in by andersk, 16 years ago

Add a description field.

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