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

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

404 errors are not email-worthy.

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