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

Last change on this file since 660 was 640, checked in by ecprice, 17 years ago

Import random before seeding

  • Property svn:executable set to *
File size: 25.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
[640]14import random
[205]15from StringIO import StringIO
[113]16
[205]17def revertStandardError():
18    """Move stderr to stdout, and return the contents of the old stderr."""
19    errio = sys.stderr
20    if not isinstance(errio, StringIO):
[599]21        return ''
[205]22    sys.stderr = sys.stdout
23    errio.seek(0)
24    return errio.read()
25
26def printError():
27    """Revert stderr to stdout, and print the contents of stderr"""
28    if isinstance(sys.stderr, StringIO):
29        print revertStandardError()
30
31if __name__ == '__main__':
32    import atexit
33    atexit.register(printError)
34
[235]35import templates
[113]36from Cheetah.Template import Template
[440]37import sipb_xen_database
[443]38from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
[209]39import validation
[446]40import cache_acls
[578]41from webcommon import InvalidInput, CodeError, State
[209]42import controls
[632]43from getafsgroups import getAfsGroupMembers
[113]44
[632]45def pathSplit(path):
46    if path.startswith('/'):
47        path = path[1:]
48    i = path.find('/')
49    if i == -1:
50        i = len(path)
51    return path[:i], path[i:]
52
[235]53class Checkpoint:
54    def __init__(self):
55        self.start_time = time.time()
56        self.checkpoints = []
57
58    def checkpoint(self, s):
59        self.checkpoints.append((s, time.time()))
60
61    def __str__(self):
62        return ('Timing info:\n%s\n' %
63                '\n'.join(['%s: %s' % (d, t - self.start_time) for
64                           (d, t) in self.checkpoints]))
65
66checkpoint = Checkpoint()
67
[447]68def jquote(string):
69    return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
[235]70
[205]71def helppopup(subj):
72    """Return HTML code for a (?) link to a specified help topic"""
[447]73    return ('<span class="helplink"><a href="help?' +
74            cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
75            +'" target="_blank" ' +
76            'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
[205]77
78def makeErrorPre(old, addition):
79    if addition is None:
80        return
81    if old:
82        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
83    else:
84        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
[139]85
[440]86Template.sipb_xen_database = sipb_xen_database
[205]87Template.helppopup = staticmethod(helppopup)
88Template.err = None
[139]89
[205]90class JsonDict:
91    """Class to store a dictionary that will be converted to JSON"""
92    def __init__(self, **kws):
93        self.data = kws
94        if 'err' in kws:
95            err = kws['err']
96            del kws['err']
97            self.addError(err)
[139]98
[205]99    def __str__(self):
100        return simplejson.dumps(self.data)
101
102    def addError(self, text):
103        """Add stderr text to be displayed on the website."""
104        self.data['err'] = \
105            makeErrorPre(self.data.get('err'), text)
106
107class Defaults:
108    """Class to store default values for fields."""
109    memory = 256
110    disk = 4.0
111    cdrom = ''
[443]112    autoinstall = ''
[205]113    name = ''
[609]114    description = ''
[515]115    type = 'linux-hvm'
116
[205]117    def __init__(self, max_memory=None, max_disk=None, **kws):
118        if max_memory is not None:
119            self.memory = min(self.memory, max_memory)
120        if max_disk is not None:
121            self.max_disk = min(self.disk, max_disk)
122        for key in kws:
123            setattr(self, key, kws[key])
124
125
126
[209]127DEFAULT_HEADERS = {'Content-Type': 'text/html'}
[205]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):
[629]147    kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
[577]148    validate = validation.Validate(username, state, strict=True, **kws)
[609]149    return dict(contact=username, name=validate.name, description=validate.description, memory=validate.memory,
[572]150                disksize=validate.disksize, owner=validate.owner, machine_type=validate.vmtype,
151                cdrom=getattr(validate, 'cdrom', None),
[629]152                autoinstall=getattr(validate, 'autoinstall', None))
[134]153
[632]154def create(username, state, path, 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
[632]217def listVms(username, state, path, 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
[632]224def vnc(username, state, path, 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
[632]321def command(username, state, path, 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',
[633]345                 'Location': 'info?machine_id=%d' % machine.machine_id},
[407]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:
[609]358        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
[572]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
[609]383        if hasattr(validate, 'description'):
384            machine.description = validate.description
[572]385        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
386            machine.administrator = validate.admin
[446]387            update_acl = True
[572]388        if hasattr(validate, 'contact'):
389            machine.contact = validate.contact
[438]390
[161]391        ctx.current.save(machine)
[446]392        if update_acl:
[572]393            print >> sys.stderr, machine, machine.administrator
[446]394            cache_acls.refreshMachine(machine)
[161]395        transaction.commit()
396    except:
397        transaction.rollback()
[163]398        raise
[177]399    for diskname in olddisk:
[209]400        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
[572]401    if hasattr(validate, 'name'):
402        controls.renameMachine(machine, oldname, validate.name)
403    return dict(user=username,
404                command="modify",
[205]405                machine=machine)
[438]406
[632]407def modify(username, state, path, fields):
[205]408    """Handler for modifying attributes of a machine."""
409    try:
[572]410        modify_dict = modifyDict(username, state, fields)
[205]411    except InvalidInput, err:
[207]412        result = None
[572]413        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[205]414    else:
415        machine = modify_dict['machine']
[209]416        result = 'Success!'
[205]417        err = None
[585]418    info_dict = infoDict(username, state, machine)
[205]419    info_dict['err'] = err
420    if err:
421        for field in fields.keys():
422            setattr(info_dict['defaults'], field, fields.getfirst(field))
[207]423    info_dict['result'] = result
[235]424    return templates.info(searchList=[info_dict])
[161]425
[438]426
[632]427def helpHandler(username, state, path, fields):
[145]428    """Handler for help messages."""
[139]429    simple = fields.getfirst('simple')
430    subjects = fields.getlist('subject')
[438]431
[536]432    help_mapping = {'ParaVM Console': """
[432]433ParaVM machines do not support local console access over VNC.  To
434access the serial console of these machines, you can SSH with Kerberos
[536]435to console.xvm.mit.edu, using the name of the machine as your
[432]436username.""",
[536]437                    'HVM/ParaVM': """
[139]438HVM machines use the virtualization features of the processor, while
439ParaVM machines use Xen's emulation of virtualization features.  You
440want an HVM virtualized machine.""",
[536]441                    'CPU Weight': """
[205]442Don't ask us!  We're as mystified as you are.""",
[536]443                    'Owner': """
[205]444The owner field is used to determine <a
[536]445href="help?subject=Quotas">quotas</a>.  It must be the name of a
[205]446locker that you are an AFS administrator of.  In particular, you or an
447AFS group you are a member of must have AFS rlidwka bits on the
[432]448locker.  You can check who administers the LOCKER locker using the
449commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
[536]450href="help?subject=Administrator">administrator</a>.""",
451                    'Administrator': """
[205]452The administrator field determines who can access the console and
453power on and off the machine.  This can be either a user or a moira
454group.""",
[536]455                    'Quotas': """
[408]456Quotas are determined on a per-locker basis.  Each locker may have a
[205]457maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
[309]458active machines.""",
[536]459                    'Console': """
[309]460<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
461setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
462your machine will run just fine, but the applet's display of the
463console will suffer artifacts.
464"""
[536]465                    }
[438]466
[187]467    if not subjects:
[205]468        subjects = sorted(help_mapping.keys())
[438]469
[572]470    d = dict(user=username,
[139]471             simple=simple,
472             subjects=subjects,
[205]473             mapping=help_mapping)
[438]474
[235]475    return templates.help(searchList=[d])
[133]476
[438]477
[632]478def badOperation(u, s, p, e):
[438]479    """Function called when accessing an unknown URI."""
[607]480    return ({'Status': '404 Not Found'}, 'Invalid operation.')
[205]481
[579]482def infoDict(username, state, machine):
[438]483    """Get the variables used by info.tmpl."""
[209]484    status = controls.statusInfo(machine)
[235]485    checkpoint.checkpoint('Getting status info')
[133]486    has_vnc = hasVnc(status)
487    if status is None:
488        main_status = dict(name=machine.name,
489                           memory=str(machine.memory))
[205]490        uptime = None
491        cputime = None
[133]492    else:
493        main_status = dict(status[1:])
[167]494        start_time = float(main_status.get('start_time', 0))
495        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
496        cpu_time_float = float(main_status.get('cpu_time', 0))
497        cputime = datetime.timedelta(seconds=int(cpu_time_float))
[235]498    checkpoint.checkpoint('Status')
[133]499    display_fields = """name uptime memory state cpu_weight on_reboot
500     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
501    display_fields = [('name', 'Name'),
[609]502                      ('description', 'Description'),
[133]503                      ('owner', 'Owner'),
[187]504                      ('administrator', 'Administrator'),
[133]505                      ('contact', 'Contact'),
[136]506                      ('type', 'Type'),
[133]507                      'NIC_INFO',
508                      ('uptime', 'uptime'),
509                      ('cputime', 'CPU usage'),
510                      ('memory', 'RAM'),
511                      'DISK_INFO',
512                      ('state', 'state (xen format)'),
[536]513                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
[133]514                      ('on_reboot', 'Action on VM reboot'),
515                      ('on_poweroff', 'Action on VM poweroff'),
516                      ('on_crash', 'Action on VM crash'),
517                      ('on_xend_start', 'Action on Xen start'),
518                      ('on_xend_stop', 'Action on Xen stop'),
519                      ('bootloader', 'Bootloader options'),
520                      ]
521    fields = []
522    machine_info = {}
[147]523    machine_info['name'] = machine.name
[609]524    machine_info['description'] = machine.description
[136]525    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]526    machine_info['owner'] = machine.owner
[187]527    machine_info['administrator'] = machine.administrator
[133]528    machine_info['contact'] = machine.contact
529
530    nic_fields = getNicInfo(machine_info, machine)
531    nic_point = display_fields.index('NIC_INFO')
[438]532    display_fields = (display_fields[:nic_point] + nic_fields +
[205]533                      display_fields[nic_point+1:])
[133]534
535    disk_fields = getDiskInfo(machine_info, machine)
536    disk_point = display_fields.index('DISK_INFO')
[438]537    display_fields = (display_fields[:disk_point] + disk_fields +
[205]538                      display_fields[disk_point+1:])
[438]539
[211]540    main_status['memory'] += ' MiB'
[133]541    for field, disp in display_fields:
[167]542        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]543            fields.append((disp, locals()[field]))
[147]544        elif field in machine_info:
545            fields.append((disp, machine_info[field]))
[133]546        elif field in main_status:
547            fields.append((disp, main_status[field]))
548        else:
549            pass
550            #fields.append((disp, None))
[235]551
552    checkpoint.checkpoint('Got fields')
553
554
[572]555    max_mem = validation.maxMemory(machine.owner, state, machine, False)
[235]556    checkpoint.checkpoint('Got mem')
[566]557    max_disk = validation.maxDisk(machine.owner, machine)
[209]558    defaults = Defaults()
[609]559    for name in 'machine_id name description administrator owner memory contact'.split():
[205]560        setattr(defaults, name, getattr(machine, name))
[516]561    defaults.type = machine.type.type_id
[205]562    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
[235]563    checkpoint.checkpoint('Got defaults')
[572]564    d = dict(user=username,
[133]565             on=status is not None,
566             machine=machine,
[205]567             defaults=defaults,
[133]568             has_vnc=has_vnc,
569             uptime=str(uptime),
570             ram=machine.memory,
[144]571             max_mem=max_mem,
572             max_disk=max_disk,
[536]573             owner_help=helppopup("Owner"),
[133]574             fields = fields)
[205]575    return d
[113]576
[632]577def info(username, state, path, fields):
[205]578    """Handler for info on a single VM."""
[572]579    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[579]580    d = infoDict(username, state, machine)
[235]581    checkpoint.checkpoint('Got infodict')
582    return templates.info(searchList=[d])
[205]583
[632]584def unauthFront(_, _2, _3, fields):
[510]585    """Information for unauth'd users."""
586    return templates.unauth(searchList=[{'simple' : True}])
587
[632]588def overlord(username, state, path, fields):
[633]589    if path == '':
590        return ({'Status': '303 See Other',
591                 'Location': 'overlord/'},
592                "You shouldn't see this message.")
[632]593    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
594        raise InvalidInput('username', username, 'Not an overlord.')
595    newstate = State(username, overlord=True)
596    newstate.environ = state.environ
597    return handler(username, newstate, path, fields)
598
599def throwError(_, __, ___, ____):
[598]600    """Throw an error, to test the error-tracing mechanisms."""
[602]601    raise RuntimeError("test of the emergency broadcast system")
[598]602
[113]603mapping = dict(list=listVms,
604               vnc=vnc,
[133]605               command=command,
606               modify=modify,
[113]607               info=info,
[139]608               create=create,
[510]609               help=helpHandler,
[598]610               unauth=unauthFront,
[632]611               overlord=overlord,
[598]612               errortest=throwError)
[113]613
[205]614def printHeaders(headers):
[438]615    """Print a dictionary as HTTP headers."""
[205]616    for key, value in headers.iteritems():
617        print '%s: %s' % (key, value)
618    print
619
[598]620def send_error_mail(subject, body):
621    import subprocess
[205]622
[598]623    to = 'xvm@mit.edu'
624    mail = """To: %s
625From: root@xvm.mit.edu
626Subject: %s
627
628%s
629""" % (to, subject, body)
630    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
631    p.stdin.write(mail)
632    p.stdin.close()
633    p.wait()
634
[603]635def show_error(op, username, fields, err, emsg, traceback):
636    """Print an error page when an exception occurs"""
637    d = dict(op=op, user=username, fields=fields,
638             errorMessage=str(err), stderr=emsg, traceback=traceback)
639    details = templates.error_raw(searchList=[d])
[627]640    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
641        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
642                        details)
[603]643    d['details'] = details
644    return templates.error(searchList=[d])
645
[572]646def getUser(environ):
[205]647    """Return the current user based on the SSL environment variables"""
[629]648    return environ.get('REMOTE_USER', None)
[205]649
[632]650def handler(username, state, path, fields):
651    operation, path = pathSplit(path)
652    if not operation:
653        operation = 'list'
654    print 'Starting', operation
655    fun = mapping.get(operation, badOperation)
656    return fun(username, state, path, fields)
657
[579]658class App:
659    def __init__(self, environ, start_response):
660        self.environ = environ
661        self.start = start_response
[205]662
[579]663        self.username = getUser(environ)
664        self.state = State(self.username)
[581]665        self.state.environ = environ
[205]666
[634]667        random.seed() #sigh
668
[579]669    def __iter__(self):
[632]670        start_time = time.time()
[613]671        sipb_xen_database.clear_cache()
[600]672        sys.stderr = StringIO()
[579]673        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
674        operation = self.environ.get('PATH_INFO', '')
675        if not operation:
[633]676            self.start("301 Moved Permanently", [('Location', './')])
[579]677            return
678        if self.username is None:
679            operation = 'unauth'
680
681        try:
682            checkpoint.checkpoint('Before')
[632]683            output = handler(self.username, self.state, operation, fields)
[579]684            checkpoint.checkpoint('After')
685
686            headers = dict(DEFAULT_HEADERS)
687            if isinstance(output, tuple):
688                new_headers, output = output
689                headers.update(new_headers)
690            e = revertStandardError()
691            if e:
692                if isinstance(output, basestring):
693                    sys.stderr = StringIO()
694                    x = str(output)
695                    print >> sys.stderr, x
696                    print >> sys.stderr, 'XXX'
697                    print >> sys.stderr, e
698                    raise Exception()
699                output.addError(e)
700            output_string =  str(output)
701            checkpoint.checkpoint('output as a string')
702        except Exception, err:
703            if not fields.has_key('js'):
704                if isinstance(err, InvalidInput):
705                    self.start('200 OK', [('Content-Type', 'text/html')])
706                    e = revertStandardError()
[603]707                    yield str(invalidInput(operation, self.username, fields,
708                                           err, e))
[579]709                    return
[602]710            import traceback
711            self.start('500 Internal Server Error',
712                       [('Content-Type', 'text/html')])
713            e = revertStandardError()
[603]714            s = show_error(operation, self.username, fields,
[602]715                           err, e, traceback.format_exc())
716            yield str(s)
717            return
[587]718        status = headers.setdefault('Status', '200 OK')
719        del headers['Status']
720        self.start(status, headers.items())
[579]721        yield output_string
[535]722        if fields.has_key('timedebug'):
[579]723            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
[209]724
[579]725def constructor():
726    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
727    return App
[535]728
[579]729def main():
730    from flup.server.fcgi_fork import WSGIServer
731    WSGIServer(constructor()).run()
[535]732
[579]733if __name__ == '__main__':
734    main()
Note: See TracBrowser for help on using the repository browser.