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

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

web: say where a VM is running

  • Property svn:executable set to *
File size: 25.9 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:])
[662]494        main_status['host'] = controls.listHost(machine)
[167]495        start_time = float(main_status.get('start_time', 0))
496        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
497        cpu_time_float = float(main_status.get('cpu_time', 0))
498        cputime = datetime.timedelta(seconds=int(cpu_time_float))
[235]499    checkpoint.checkpoint('Status')
[133]500    display_fields = """name uptime memory state cpu_weight on_reboot
501     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
502    display_fields = [('name', 'Name'),
[609]503                      ('description', 'Description'),
[133]504                      ('owner', 'Owner'),
[187]505                      ('administrator', 'Administrator'),
[133]506                      ('contact', 'Contact'),
[136]507                      ('type', 'Type'),
[133]508                      'NIC_INFO',
509                      ('uptime', 'uptime'),
510                      ('cputime', 'CPU usage'),
[662]511                      ('host', 'Hosted on'),
[133]512                      ('memory', 'RAM'),
513                      'DISK_INFO',
514                      ('state', 'state (xen format)'),
[536]515                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
[133]516                      ('on_reboot', 'Action on VM reboot'),
517                      ('on_poweroff', 'Action on VM poweroff'),
518                      ('on_crash', 'Action on VM crash'),
519                      ('on_xend_start', 'Action on Xen start'),
520                      ('on_xend_stop', 'Action on Xen stop'),
521                      ('bootloader', 'Bootloader options'),
522                      ]
523    fields = []
524    machine_info = {}
[147]525    machine_info['name'] = machine.name
[609]526    machine_info['description'] = machine.description
[136]527    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]528    machine_info['owner'] = machine.owner
[187]529    machine_info['administrator'] = machine.administrator
[133]530    machine_info['contact'] = machine.contact
531
532    nic_fields = getNicInfo(machine_info, machine)
533    nic_point = display_fields.index('NIC_INFO')
[438]534    display_fields = (display_fields[:nic_point] + nic_fields +
[205]535                      display_fields[nic_point+1:])
[133]536
537    disk_fields = getDiskInfo(machine_info, machine)
538    disk_point = display_fields.index('DISK_INFO')
[438]539    display_fields = (display_fields[:disk_point] + disk_fields +
[205]540                      display_fields[disk_point+1:])
[438]541
[211]542    main_status['memory'] += ' MiB'
[133]543    for field, disp in display_fields:
[167]544        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]545            fields.append((disp, locals()[field]))
[147]546        elif field in machine_info:
547            fields.append((disp, machine_info[field]))
[133]548        elif field in main_status:
549            fields.append((disp, main_status[field]))
550        else:
551            pass
552            #fields.append((disp, None))
[235]553
554    checkpoint.checkpoint('Got fields')
555
556
[572]557    max_mem = validation.maxMemory(machine.owner, state, machine, False)
[235]558    checkpoint.checkpoint('Got mem')
[566]559    max_disk = validation.maxDisk(machine.owner, machine)
[209]560    defaults = Defaults()
[609]561    for name in 'machine_id name description administrator owner memory contact'.split():
[205]562        setattr(defaults, name, getattr(machine, name))
[516]563    defaults.type = machine.type.type_id
[205]564    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
[235]565    checkpoint.checkpoint('Got defaults')
[572]566    d = dict(user=username,
[133]567             on=status is not None,
568             machine=machine,
[205]569             defaults=defaults,
[133]570             has_vnc=has_vnc,
571             uptime=str(uptime),
572             ram=machine.memory,
[144]573             max_mem=max_mem,
574             max_disk=max_disk,
[536]575             owner_help=helppopup("Owner"),
[133]576             fields = fields)
[205]577    return d
[113]578
[632]579def info(username, state, path, fields):
[205]580    """Handler for info on a single VM."""
[572]581    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[579]582    d = infoDict(username, state, machine)
[235]583    checkpoint.checkpoint('Got infodict')
584    return templates.info(searchList=[d])
[205]585
[632]586def unauthFront(_, _2, _3, fields):
[510]587    """Information for unauth'd users."""
588    return templates.unauth(searchList=[{'simple' : True}])
589
[632]590def overlord(username, state, path, fields):
[633]591    if path == '':
592        return ({'Status': '303 See Other',
593                 'Location': 'overlord/'},
594                "You shouldn't see this message.")
[632]595    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
596        raise InvalidInput('username', username, 'Not an overlord.')
597    newstate = State(username, overlord=True)
598    newstate.environ = state.environ
599    return handler(username, newstate, path, fields)
600
601def throwError(_, __, ___, ____):
[598]602    """Throw an error, to test the error-tracing mechanisms."""
[602]603    raise RuntimeError("test of the emergency broadcast system")
[598]604
[113]605mapping = dict(list=listVms,
606               vnc=vnc,
[133]607               command=command,
608               modify=modify,
[113]609               info=info,
[139]610               create=create,
[510]611               help=helpHandler,
[598]612               unauth=unauthFront,
[632]613               overlord=overlord,
[598]614               errortest=throwError)
[113]615
[205]616def printHeaders(headers):
[438]617    """Print a dictionary as HTTP headers."""
[205]618    for key, value in headers.iteritems():
619        print '%s: %s' % (key, value)
620    print
621
[598]622def send_error_mail(subject, body):
623    import subprocess
[205]624
[598]625    to = 'xvm@mit.edu'
626    mail = """To: %s
627From: root@xvm.mit.edu
628Subject: %s
629
630%s
631""" % (to, subject, body)
632    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
633    p.stdin.write(mail)
634    p.stdin.close()
635    p.wait()
636
[603]637def show_error(op, username, fields, err, emsg, traceback):
638    """Print an error page when an exception occurs"""
639    d = dict(op=op, user=username, fields=fields,
640             errorMessage=str(err), stderr=emsg, traceback=traceback)
641    details = templates.error_raw(searchList=[d])
[627]642    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
643        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
644                        details)
[603]645    d['details'] = details
646    return templates.error(searchList=[d])
647
[572]648def getUser(environ):
[205]649    """Return the current user based on the SSL environment variables"""
[629]650    return environ.get('REMOTE_USER', None)
[205]651
[632]652def handler(username, state, path, fields):
653    operation, path = pathSplit(path)
654    if not operation:
655        operation = 'list'
656    print 'Starting', operation
657    fun = mapping.get(operation, badOperation)
658    return fun(username, state, path, fields)
659
[579]660class App:
661    def __init__(self, environ, start_response):
662        self.environ = environ
663        self.start = start_response
[205]664
[579]665        self.username = getUser(environ)
666        self.state = State(self.username)
[581]667        self.state.environ = environ
[205]668
[634]669        random.seed() #sigh
670
[579]671    def __iter__(self):
[632]672        start_time = time.time()
[613]673        sipb_xen_database.clear_cache()
[600]674        sys.stderr = StringIO()
[579]675        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
676        operation = self.environ.get('PATH_INFO', '')
677        if not operation:
[633]678            self.start("301 Moved Permanently", [('Location', './')])
[579]679            return
680        if self.username is None:
681            operation = 'unauth'
682
683        try:
684            checkpoint.checkpoint('Before')
[632]685            output = handler(self.username, self.state, operation, fields)
[579]686            checkpoint.checkpoint('After')
687
688            headers = dict(DEFAULT_HEADERS)
689            if isinstance(output, tuple):
690                new_headers, output = output
691                headers.update(new_headers)
692            e = revertStandardError()
693            if e:
694                if isinstance(output, basestring):
695                    sys.stderr = StringIO()
696                    x = str(output)
697                    print >> sys.stderr, x
698                    print >> sys.stderr, 'XXX'
699                    print >> sys.stderr, e
700                    raise Exception()
701                output.addError(e)
702            output_string =  str(output)
703            checkpoint.checkpoint('output as a string')
704        except Exception, err:
705            if not fields.has_key('js'):
706                if isinstance(err, InvalidInput):
707                    self.start('200 OK', [('Content-Type', 'text/html')])
708                    e = revertStandardError()
[603]709                    yield str(invalidInput(operation, self.username, fields,
710                                           err, e))
[579]711                    return
[602]712            import traceback
713            self.start('500 Internal Server Error',
714                       [('Content-Type', 'text/html')])
715            e = revertStandardError()
[603]716            s = show_error(operation, self.username, fields,
[602]717                           err, e, traceback.format_exc())
718            yield str(s)
719            return
[587]720        status = headers.setdefault('Status', '200 OK')
721        del headers['Status']
722        self.start(status, headers.items())
[579]723        yield output_string
[535]724        if fields.has_key('timedebug'):
[579]725            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
[209]726
[579]727def constructor():
728    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
729    return App
[535]730
[579]731def main():
732    from flup.server.fcgi_fork import WSGIServer
733    WSGIServer(constructor()).run()
[535]734
[579]735if __name__ == '__main__':
736    main()
Note: See TracBrowser for help on using the repository browser.