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

Last change on this file since 638 was 634, checked in by ecprice, 16 years ago

Seed the random number generator after the fork.

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