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
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 = ''
[609]106    description = ''
[515]107    type = 'linux-hvm'
108
[205]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
[209]119DEFAULT_HEADERS = {'Content-Type': 'text/html'}
[205]120
[572]121def invalidInput(op, username, fields, err, emsg):
[153]122    """Print an error page when an InvalidInput exception occurs"""
[572]123    d = dict(op=op, user=username, err_field=err.err_field,
[153]124             err_value=str(err.err_value), stderr=emsg,
125             errorMessage=str(err))
[235]126    return templates.invalid(searchList=[d])
[153]127
[119]128def hasVnc(status):
[133]129    """Does the machine with a given status list support VNC?"""
[119]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
[572]138def parseCreate(username, state, fields):
[609]139    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom clone_from'.split()])
[577]140    validate = validation.Validate(username, state, strict=True, **kws)
[609]141    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
[572]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))
[134]145
[572]146def create(username, state, fields):
[205]147    """Handler for create requests."""
148    try:
[572]149        parsed_fields = parseCreate(username, state, fields)
[577]150        machine = controls.createVm(username, state, **parsed_fields)
[205]151    except InvalidInput, err:
[207]152        pass
[205]153    else:
154        err = None
[572]155    state.clear() #Changed global state
[576]156    d = getListDict(username, state)
[205]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']
[235]163    return templates.list(searchList=[d])
[205]164
165
[572]166def getListDict(username, state):
[438]167    """Gets the list of local variables used by list.tmpl."""
[535]168    checkpoint.checkpoint('Starting')
[572]169    machines = state.machines
[235]170    checkpoint.checkpoint('Got my machines')
[133]171    on = {}
[119]172    has_vnc = {}
[572]173    xmlist = state.xmlist
[235]174    checkpoint.checkpoint('Got uptimes')
[572]175    can_clone = 'ice3' not in state.xmlist_raw
[136]176    for m in machines:
[535]177        if m not in xmlist:
[144]178            has_vnc[m] = 'Off'
[535]179            m.uptime = None
[136]180        else:
[535]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:
[536]187                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
[572]188    max_memory = validation.maxMemory(username, state)
189    max_disk = validation.maxDisk(username)
[235]190    checkpoint.checkpoint('Got max mem/disk')
[205]191    defaults = Defaults(max_memory=max_memory,
192                        max_disk=max_disk,
[572]193                        owner=username,
[205]194                        cdrom='gutsy-i386')
[235]195    checkpoint.checkpoint('Got defaults')
[424]196    def sortkey(machine):
[572]197        return (machine.owner != username, machine.owner, machine.name)
[424]198    machines = sorted(machines, key=sortkey)
[572]199    d = dict(user=username,
200             cant_add_vm=validation.cantAddVm(username, state),
[205]201             max_memory=max_memory,
[144]202             max_disk=max_disk,
[205]203             defaults=defaults,
[113]204             machines=machines,
[540]205             has_vnc=has_vnc,
206             can_clone=can_clone)
[205]207    return d
[113]208
[572]209def listVms(username, state, fields):
[205]210    """Handler for list requests."""
[235]211    checkpoint.checkpoint('Getting list dict')
[572]212    d = getListDict(username, state)
[235]213    checkpoint.checkpoint('Got list dict')
214    return templates.list(searchList=[d])
[438]215
[572]216def vnc(username, state, fields):
[119]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
[205]226    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
[438]227      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
[205]228    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
[438]229      --dport 10003 -j SNAT --to-source 18.187.7.142
[205]230    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
231      --dport 10003 -j ACCEPT
[145]232
233    Remember to enable iptables!
234    echo 1 > /proc/sys/net/ipv4/ip_forward
[119]235    """
[572]236    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[438]237
[118]238    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
239
240    data = {}
[572]241    data["user"] = username
[205]242    data["machine"] = machine.name
243    data["expires"] = time.time()+(5*60)
244    pickled_data = cPickle.dumps(data)
[118]245    m = hmac.new(TOKEN_KEY, digestmod=sha)
[205]246    m.update(pickled_data)
247    token = {'data': pickled_data, 'digest': m.digest()}
[118]248    token = cPickle.dumps(token)
249    token = base64.urlsafe_b64encode(token)
[438]250
[209]251    status = controls.statusInfo(machine)
[152]252    has_vnc = hasVnc(status)
[438]253
[572]254    d = dict(user=username,
[152]255             on=status,
256             has_vnc=has_vnc,
[113]257             machine=machine,
[581]258             hostname=state.environ.get('SERVER_NAME', 'localhost'),
[113]259             authtoken=token)
[235]260    return templates.vnc(searchList=[d])
[113]261
[252]262def getHostname(nic):
[438]263    """Find the hostname associated with a NIC.
264
265    XXX this should be merged with the similar logic in DNS and DHCP.
266    """
[252]267    if nic.hostname and '.' in nic.hostname:
268        return nic.hostname
269    elif nic.machine:
[507]270        return nic.machine.name + '.xvm.mit.edu'
[252]271    else:
272        return None
273
274
[133]275def getNicInfo(data_dict, machine):
[145]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    """
[133]281    data_dict['num_nics'] = len(machine.nics)
[227]282    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
[133]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])
[227]289        if not i:
[252]290            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
[133]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):
[145]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    """
[133]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
[438]308        disk_fields.extend([(x % name, y % name) for x, y in
[205]309                            disk_fields_template])
[211]310        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
[133]311    return disk_fields
312
[572]313def command(username, state, fields):
[205]314    """Handler for running commands like boot and delete on a VM."""
[207]315    back = fields.getfirst('back')
[205]316    try:
[572]317        d = controls.commandResult(username, state, fields)
[207]318        if d['command'] == 'Delete VM':
319            back = 'list'
[205]320    except InvalidInput, err:
[207]321        if not back:
[205]322            raise
[572]323        print >> sys.stderr, err
[261]324        result = err
[205]325    else:
326        result = 'Success!'
[207]327        if not back:
[235]328            return templates.command(searchList=[d])
[207]329    if back == 'list':
[572]330        state.clear() #Changed global state
[576]331        d = getListDict(username, state)
[207]332        d['result'] = result
[235]333        return templates.list(searchList=[d])
[207]334    elif back == 'info':
[572]335        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[588]336        return ({'Status': '303 See Other',
[407]337                 'Location': '/info?machine_id=%d' % machine.machine_id},
338                "You shouldn't see this message.")
[205]339    else:
[261]340        raise InvalidInput('back', back, 'Not a known back page.')
[205]341
[572]342def modifyDict(username, state, fields):
[438]343    """Modify a machine as specified by CGI arguments.
344
345    Return a list of local variables for modify.tmpl.
346    """
[177]347    olddisk = {}
[161]348    transaction = ctx.current.create_transaction()
349    try:
[609]350        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
[572]351        validate = validation.Validate(username, state, **kws)
352        machine = validate.machine
[161]353        oldname = machine.name
[153]354
[572]355        if hasattr(validate, 'memory'):
356            machine.memory = validate.memory
[438]357
[572]358        if hasattr(validate, 'vmtype'):
359            machine.type = validate.vmtype
[440]360
[572]361        if hasattr(validate, 'disksize'):
362            disksize = validate.disksize
[177]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)
[438]368
[446]369        update_acl = False
[572]370        if hasattr(validate, 'owner') and validate.owner != machine.owner:
371            machine.owner = validate.owner
[446]372            update_acl = True
[572]373        if hasattr(validate, 'name'):
[586]374            machine.name = validate.name
[609]375        if hasattr(validate, 'description'):
376            machine.description = validate.description
[572]377        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
378            machine.administrator = validate.admin
[446]379            update_acl = True
[572]380        if hasattr(validate, 'contact'):
381            machine.contact = validate.contact
[438]382
[161]383        ctx.current.save(machine)
[446]384        if update_acl:
[572]385            print >> sys.stderr, machine, machine.administrator
[446]386            cache_acls.refreshMachine(machine)
[161]387        transaction.commit()
388    except:
389        transaction.rollback()
[163]390        raise
[177]391    for diskname in olddisk:
[209]392        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
[572]393    if hasattr(validate, 'name'):
394        controls.renameMachine(machine, oldname, validate.name)
395    return dict(user=username,
396                command="modify",
[205]397                machine=machine)
[438]398
[572]399def modify(username, state, fields):
[205]400    """Handler for modifying attributes of a machine."""
401    try:
[572]402        modify_dict = modifyDict(username, state, fields)
[205]403    except InvalidInput, err:
[207]404        result = None
[572]405        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[205]406    else:
407        machine = modify_dict['machine']
[209]408        result = 'Success!'
[205]409        err = None
[585]410    info_dict = infoDict(username, state, machine)
[205]411    info_dict['err'] = err
412    if err:
413        for field in fields.keys():
414            setattr(info_dict['defaults'], field, fields.getfirst(field))
[207]415    info_dict['result'] = result
[235]416    return templates.info(searchList=[info_dict])
[161]417
[438]418
[572]419def helpHandler(username, state, fields):
[145]420    """Handler for help messages."""
[139]421    simple = fields.getfirst('simple')
422    subjects = fields.getlist('subject')
[438]423
[536]424    help_mapping = {'ParaVM Console': """
[432]425ParaVM machines do not support local console access over VNC.  To
426access the serial console of these machines, you can SSH with Kerberos
[536]427to console.xvm.mit.edu, using the name of the machine as your
[432]428username.""",
[536]429                    'HVM/ParaVM': """
[139]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.""",
[536]433                    'CPU Weight': """
[205]434Don't ask us!  We're as mystified as you are.""",
[536]435                    'Owner': """
[205]436The owner field is used to determine <a
[536]437href="help?subject=Quotas">quotas</a>.  It must be the name of a
[205]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
[432]440locker.  You can check who administers the LOCKER locker using the
441commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
[536]442href="help?subject=Administrator">administrator</a>.""",
443                    'Administrator': """
[205]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.""",
[536]447                    'Quotas': """
[408]448Quotas are determined on a per-locker basis.  Each locker may have a
[205]449maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
[309]450active machines.""",
[536]451                    'Console': """
[309]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"""
[536]457                    }
[438]458
[187]459    if not subjects:
[205]460        subjects = sorted(help_mapping.keys())
[438]461
[572]462    d = dict(user=username,
[139]463             simple=simple,
464             subjects=subjects,
[205]465             mapping=help_mapping)
[438]466
[235]467    return templates.help(searchList=[d])
[133]468
[438]469
[579]470def badOperation(u, s, e):
[438]471    """Function called when accessing an unknown URI."""
[607]472    return ({'Status': '404 Not Found'}, 'Invalid operation.')
[205]473
[579]474def infoDict(username, state, machine):
[438]475    """Get the variables used by info.tmpl."""
[209]476    status = controls.statusInfo(machine)
[235]477    checkpoint.checkpoint('Getting status info')
[133]478    has_vnc = hasVnc(status)
479    if status is None:
480        main_status = dict(name=machine.name,
481                           memory=str(machine.memory))
[205]482        uptime = None
483        cputime = None
[133]484    else:
485        main_status = dict(status[1:])
[167]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))
[235]490    checkpoint.checkpoint('Status')
[133]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'),
[609]494                      ('description', 'Description'),
[133]495                      ('owner', 'Owner'),
[187]496                      ('administrator', 'Administrator'),
[133]497                      ('contact', 'Contact'),
[136]498                      ('type', 'Type'),
[133]499                      'NIC_INFO',
500                      ('uptime', 'uptime'),
501                      ('cputime', 'CPU usage'),
502                      ('memory', 'RAM'),
503                      'DISK_INFO',
504                      ('state', 'state (xen format)'),
[536]505                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
[133]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 = {}
[147]515    machine_info['name'] = machine.name
[609]516    machine_info['description'] = machine.description
[136]517    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]518    machine_info['owner'] = machine.owner
[187]519    machine_info['administrator'] = machine.administrator
[133]520    machine_info['contact'] = machine.contact
521
522    nic_fields = getNicInfo(machine_info, machine)
523    nic_point = display_fields.index('NIC_INFO')
[438]524    display_fields = (display_fields[:nic_point] + nic_fields +
[205]525                      display_fields[nic_point+1:])
[133]526
527    disk_fields = getDiskInfo(machine_info, machine)
528    disk_point = display_fields.index('DISK_INFO')
[438]529    display_fields = (display_fields[:disk_point] + disk_fields +
[205]530                      display_fields[disk_point+1:])
[438]531
[211]532    main_status['memory'] += ' MiB'
[133]533    for field, disp in display_fields:
[167]534        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]535            fields.append((disp, locals()[field]))
[147]536        elif field in machine_info:
537            fields.append((disp, machine_info[field]))
[133]538        elif field in main_status:
539            fields.append((disp, main_status[field]))
540        else:
541            pass
542            #fields.append((disp, None))
[235]543
544    checkpoint.checkpoint('Got fields')
545
546
[572]547    max_mem = validation.maxMemory(machine.owner, state, machine, False)
[235]548    checkpoint.checkpoint('Got mem')
[566]549    max_disk = validation.maxDisk(machine.owner, machine)
[209]550    defaults = Defaults()
[609]551    for name in 'machine_id name description administrator owner memory contact'.split():
[205]552        setattr(defaults, name, getattr(machine, name))
[516]553    defaults.type = machine.type.type_id
[205]554    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
[235]555    checkpoint.checkpoint('Got defaults')
[572]556    d = dict(user=username,
[133]557             on=status is not None,
558             machine=machine,
[205]559             defaults=defaults,
[133]560             has_vnc=has_vnc,
561             uptime=str(uptime),
562             ram=machine.memory,
[144]563             max_mem=max_mem,
564             max_disk=max_disk,
[536]565             owner_help=helppopup("Owner"),
[133]566             fields = fields)
[205]567    return d
[113]568
[572]569def info(username, state, fields):
[205]570    """Handler for info on a single VM."""
[572]571    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[579]572    d = infoDict(username, state, machine)
[235]573    checkpoint.checkpoint('Got infodict')
574    return templates.info(searchList=[d])
[205]575
[572]576def unauthFront(_, _2, fields):
[510]577    """Information for unauth'd users."""
578    return templates.unauth(searchList=[{'simple' : True}])
579
[598]580def throwError(_, __, ___):
581    """Throw an error, to test the error-tracing mechanisms."""
[602]582    raise RuntimeError("test of the emergency broadcast system")
[598]583
[113]584mapping = dict(list=listVms,
585               vnc=vnc,
[133]586               command=command,
587               modify=modify,
[113]588               info=info,
[139]589               create=create,
[510]590               help=helpHandler,
[598]591               unauth=unauthFront,
592               errortest=throwError)
[113]593
[205]594def printHeaders(headers):
[438]595    """Print a dictionary as HTTP headers."""
[205]596    for key, value in headers.iteritems():
597        print '%s: %s' % (key, value)
598    print
599
[598]600def send_error_mail(subject, body):
601    import subprocess
[205]602
[598]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
[603]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
[572]623def getUser(environ):
[205]624    """Return the current user based on the SSL environment variables"""
[572]625    email = environ.get('SSL_CLIENT_S_DN_Email', None)
[510]626    if email is None:
627        return None
[572]628    if not email.endswith('@MIT.EDU'):
629        return None
630    return email[:-8]
[205]631
[579]632class App:
633    def __init__(self, environ, start_response):
634        self.environ = environ
635        self.start = start_response
[205]636
[579]637        self.username = getUser(environ)
638        self.state = State(self.username)
[581]639        self.state.environ = environ
[205]640
[579]641    def __iter__(self):
[600]642        sys.stderr = StringIO()
[579]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',
[581]647                                                  self.environ['SCRIPT_NAME']+'/')])
[579]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()
[603]685                    yield str(invalidInput(operation, self.username, fields,
686                                           err, e))
[579]687                    return
[602]688            import traceback
689            self.start('500 Internal Server Error',
690                       [('Content-Type', 'text/html')])
691            e = revertStandardError()
[603]692            s = show_error(operation, self.username, fields,
[602]693                           err, e, traceback.format_exc())
694            yield str(s)
695            return
[587]696        status = headers.setdefault('Status', '200 OK')
697        del headers['Status']
698        self.start(status, headers.items())
[579]699        yield output_string
[535]700        if fields.has_key('timedebug'):
[579]701            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
[209]702
[579]703def constructor():
704    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
705    return App
[535]706
[579]707def main():
708    from flup.server.fcgi_fork import WSGIServer
709    WSGIServer(constructor()).run()
[535]710
[579]711if __name__ == '__main__':
712    main()
Note: See TracBrowser for help on using the repository browser.