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

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

collect all available data, in CodeError? case for now

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