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

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

don't give 500 for mere stdout on a redirect

  • Property svn:executable set to *
File size: 26.0 KB
RevLine 
[113]1#!/usr/bin/python
[205]2"""Main CGI script for web interface"""
[113]3
[205]4import base64
5import cPickle
[113]6import cgi
[205]7import datetime
8import hmac
9import sha
10import simplejson
11import sys
[118]12import time
[447]13import urllib
[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)
[667]258    if controls.listHost(machine) == 'sx-blade-2.mit.edu':
259        port = 10004
260    else:
261        port = 10003
[438]262
[209]263    status = controls.statusInfo(machine)
[152]264    has_vnc = hasVnc(status)
[438]265
[572]266    d = dict(user=username,
[152]267             on=status,
268             has_vnc=has_vnc,
[113]269             machine=machine,
[581]270             hostname=state.environ.get('SERVER_NAME', 'localhost'),
[667]271             port=port,
[113]272             authtoken=token)
[235]273    return templates.vnc(searchList=[d])
[113]274
[252]275def getHostname(nic):
[438]276    """Find the hostname associated with a NIC.
277
278    XXX this should be merged with the similar logic in DNS and DHCP.
279    """
[252]280    if nic.hostname and '.' in nic.hostname:
281        return nic.hostname
282    elif nic.machine:
[507]283        return nic.machine.name + '.xvm.mit.edu'
[252]284    else:
285        return None
286
287
[133]288def getNicInfo(data_dict, machine):
[145]289    """Helper function for info, get data on nics for a machine.
290
291    Modifies data_dict to include the relevant data, and returns a list
292    of (key, name) pairs to display "name: data_dict[key]" to the user.
293    """
[133]294    data_dict['num_nics'] = len(machine.nics)
[227]295    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
[133]296                           ('nic%s_mac', 'NIC %s MAC Addr'),
297                           ('nic%s_ip', 'NIC %s IP'),
298                           ]
299    nic_fields = []
300    for i in range(len(machine.nics)):
301        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
[227]302        if not i:
[252]303            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
[133]304        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
305        data_dict['nic%s_ip' % i] = machine.nics[i].ip
306    if len(machine.nics) == 1:
307        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
308    return nic_fields
309
310def getDiskInfo(data_dict, machine):
[145]311    """Helper function for info, get data on disks for a machine.
312
313    Modifies data_dict to include the relevant data, and returns a list
314    of (key, name) pairs to display "name: data_dict[key]" to the user.
315    """
[133]316    data_dict['num_disks'] = len(machine.disks)
317    disk_fields_template = [('%s_size', '%s size')]
318    disk_fields = []
319    for disk in machine.disks:
320        name = disk.guest_device_name
[438]321        disk_fields.extend([(x % name, y % name) for x, y in
[205]322                            disk_fields_template])
[211]323        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
[133]324    return disk_fields
325
[632]326def command(username, state, path, fields):
[205]327    """Handler for running commands like boot and delete on a VM."""
[207]328    back = fields.getfirst('back')
[205]329    try:
[572]330        d = controls.commandResult(username, state, fields)
[207]331        if d['command'] == 'Delete VM':
332            back = 'list'
[205]333    except InvalidInput, err:
[207]334        if not back:
[205]335            raise
[572]336        print >> sys.stderr, err
[261]337        result = err
[205]338    else:
339        result = 'Success!'
[207]340        if not back:
[235]341            return templates.command(searchList=[d])
[207]342    if back == 'list':
[572]343        state.clear() #Changed global state
[576]344        d = getListDict(username, state)
[207]345        d['result'] = result
[235]346        return templates.list(searchList=[d])
[207]347    elif back == 'info':
[572]348        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[588]349        return ({'Status': '303 See Other',
[633]350                 'Location': 'info?machine_id=%d' % machine.machine_id},
[407]351                "You shouldn't see this message.")
[205]352    else:
[261]353        raise InvalidInput('back', back, 'Not a known back page.')
[205]354
[572]355def modifyDict(username, state, fields):
[438]356    """Modify a machine as specified by CGI arguments.
357
358    Return a list of local variables for modify.tmpl.
359    """
[177]360    olddisk = {}
[161]361    transaction = ctx.current.create_transaction()
362    try:
[609]363        kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
[572]364        validate = validation.Validate(username, state, **kws)
365        machine = validate.machine
[161]366        oldname = machine.name
[153]367
[572]368        if hasattr(validate, 'memory'):
369            machine.memory = validate.memory
[438]370
[572]371        if hasattr(validate, 'vmtype'):
372            machine.type = validate.vmtype
[440]373
[572]374        if hasattr(validate, 'disksize'):
375            disksize = validate.disksize
[177]376            disk = machine.disks[0]
377            if disk.size != disksize:
378                olddisk[disk.guest_device_name] = disksize
379                disk.size = disksize
380                ctx.current.save(disk)
[438]381
[446]382        update_acl = False
[572]383        if hasattr(validate, 'owner') and validate.owner != machine.owner:
384            machine.owner = validate.owner
[446]385            update_acl = True
[572]386        if hasattr(validate, 'name'):
[586]387            machine.name = validate.name
[609]388        if hasattr(validate, 'description'):
389            machine.description = validate.description
[572]390        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
391            machine.administrator = validate.admin
[446]392            update_acl = True
[572]393        if hasattr(validate, 'contact'):
394            machine.contact = validate.contact
[438]395
[161]396        ctx.current.save(machine)
[446]397        if update_acl:
[572]398            print >> sys.stderr, machine, machine.administrator
[446]399            cache_acls.refreshMachine(machine)
[161]400        transaction.commit()
401    except:
402        transaction.rollback()
[163]403        raise
[177]404    for diskname in olddisk:
[209]405        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
[572]406    if hasattr(validate, 'name'):
407        controls.renameMachine(machine, oldname, validate.name)
408    return dict(user=username,
409                command="modify",
[205]410                machine=machine)
[438]411
[632]412def modify(username, state, path, fields):
[205]413    """Handler for modifying attributes of a machine."""
414    try:
[572]415        modify_dict = modifyDict(username, state, fields)
[205]416    except InvalidInput, err:
[207]417        result = None
[572]418        machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[205]419    else:
420        machine = modify_dict['machine']
[209]421        result = 'Success!'
[205]422        err = None
[585]423    info_dict = infoDict(username, state, machine)
[205]424    info_dict['err'] = err
425    if err:
426        for field in fields.keys():
427            setattr(info_dict['defaults'], field, fields.getfirst(field))
[207]428    info_dict['result'] = result
[235]429    return templates.info(searchList=[info_dict])
[161]430
[438]431
[632]432def helpHandler(username, state, path, fields):
[145]433    """Handler for help messages."""
[139]434    simple = fields.getfirst('simple')
435    subjects = fields.getlist('subject')
[438]436
[536]437    help_mapping = {'ParaVM Console': """
[432]438ParaVM machines do not support local console access over VNC.  To
439access the serial console of these machines, you can SSH with Kerberos
[536]440to console.xvm.mit.edu, using the name of the machine as your
[432]441username.""",
[536]442                    'HVM/ParaVM': """
[139]443HVM machines use the virtualization features of the processor, while
444ParaVM machines use Xen's emulation of virtualization features.  You
445want an HVM virtualized machine.""",
[536]446                    'CPU Weight': """
[205]447Don't ask us!  We're as mystified as you are.""",
[536]448                    'Owner': """
[205]449The owner field is used to determine <a
[536]450href="help?subject=Quotas">quotas</a>.  It must be the name of a
[205]451locker that you are an AFS administrator of.  In particular, you or an
452AFS group you are a member of must have AFS rlidwka bits on the
[432]453locker.  You can check who administers the LOCKER locker using the
454commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
[536]455href="help?subject=Administrator">administrator</a>.""",
456                    'Administrator': """
[205]457The administrator field determines who can access the console and
458power on and off the machine.  This can be either a user or a moira
459group.""",
[536]460                    'Quotas': """
[408]461Quotas are determined on a per-locker basis.  Each locker may have a
[205]462maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
[309]463active machines.""",
[536]464                    'Console': """
[309]465<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
466setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
467your machine will run just fine, but the applet's display of the
468console will suffer artifacts.
469"""
[536]470                    }
[438]471
[187]472    if not subjects:
[205]473        subjects = sorted(help_mapping.keys())
[438]474
[572]475    d = dict(user=username,
[139]476             simple=simple,
477             subjects=subjects,
[205]478             mapping=help_mapping)
[438]479
[235]480    return templates.help(searchList=[d])
[133]481
[438]482
[632]483def badOperation(u, s, p, e):
[438]484    """Function called when accessing an unknown URI."""
[607]485    return ({'Status': '404 Not Found'}, 'Invalid operation.')
[205]486
[579]487def infoDict(username, state, machine):
[438]488    """Get the variables used by info.tmpl."""
[209]489    status = controls.statusInfo(machine)
[235]490    checkpoint.checkpoint('Getting status info')
[133]491    has_vnc = hasVnc(status)
492    if status is None:
493        main_status = dict(name=machine.name,
494                           memory=str(machine.memory))
[205]495        uptime = None
496        cputime = None
[133]497    else:
498        main_status = dict(status[1:])
[662]499        main_status['host'] = controls.listHost(machine)
[167]500        start_time = float(main_status.get('start_time', 0))
501        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
502        cpu_time_float = float(main_status.get('cpu_time', 0))
503        cputime = datetime.timedelta(seconds=int(cpu_time_float))
[235]504    checkpoint.checkpoint('Status')
[133]505    display_fields = """name uptime memory state cpu_weight on_reboot
506     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
507    display_fields = [('name', 'Name'),
[609]508                      ('description', 'Description'),
[133]509                      ('owner', 'Owner'),
[187]510                      ('administrator', 'Administrator'),
[133]511                      ('contact', 'Contact'),
[136]512                      ('type', 'Type'),
[133]513                      'NIC_INFO',
514                      ('uptime', 'uptime'),
515                      ('cputime', 'CPU usage'),
[662]516                      ('host', 'Hosted on'),
[133]517                      ('memory', 'RAM'),
518                      'DISK_INFO',
519                      ('state', 'state (xen format)'),
[536]520                      ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
[133]521                      ('on_reboot', 'Action on VM reboot'),
522                      ('on_poweroff', 'Action on VM poweroff'),
523                      ('on_crash', 'Action on VM crash'),
524                      ('on_xend_start', 'Action on Xen start'),
525                      ('on_xend_stop', 'Action on Xen stop'),
526                      ('bootloader', 'Bootloader options'),
527                      ]
528    fields = []
529    machine_info = {}
[147]530    machine_info['name'] = machine.name
[609]531    machine_info['description'] = machine.description
[136]532    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]533    machine_info['owner'] = machine.owner
[187]534    machine_info['administrator'] = machine.administrator
[133]535    machine_info['contact'] = machine.contact
536
537    nic_fields = getNicInfo(machine_info, machine)
538    nic_point = display_fields.index('NIC_INFO')
[438]539    display_fields = (display_fields[:nic_point] + nic_fields +
[205]540                      display_fields[nic_point+1:])
[133]541
542    disk_fields = getDiskInfo(machine_info, machine)
543    disk_point = display_fields.index('DISK_INFO')
[438]544    display_fields = (display_fields[:disk_point] + disk_fields +
[205]545                      display_fields[disk_point+1:])
[438]546
[211]547    main_status['memory'] += ' MiB'
[133]548    for field, disp in display_fields:
[167]549        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]550            fields.append((disp, locals()[field]))
[147]551        elif field in machine_info:
552            fields.append((disp, machine_info[field]))
[133]553        elif field in main_status:
554            fields.append((disp, main_status[field]))
555        else:
556            pass
557            #fields.append((disp, None))
[235]558
559    checkpoint.checkpoint('Got fields')
560
561
[572]562    max_mem = validation.maxMemory(machine.owner, state, machine, False)
[235]563    checkpoint.checkpoint('Got mem')
[566]564    max_disk = validation.maxDisk(machine.owner, machine)
[209]565    defaults = Defaults()
[609]566    for name in 'machine_id name description administrator owner memory contact'.split():
[205]567        setattr(defaults, name, getattr(machine, name))
[516]568    defaults.type = machine.type.type_id
[205]569    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
[235]570    checkpoint.checkpoint('Got defaults')
[572]571    d = dict(user=username,
[133]572             on=status is not None,
573             machine=machine,
[205]574             defaults=defaults,
[133]575             has_vnc=has_vnc,
576             uptime=str(uptime),
577             ram=machine.memory,
[144]578             max_mem=max_mem,
579             max_disk=max_disk,
[536]580             owner_help=helppopup("Owner"),
[133]581             fields = fields)
[205]582    return d
[113]583
[632]584def info(username, state, path, fields):
[205]585    """Handler for info on a single VM."""
[572]586    machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
[579]587    d = infoDict(username, state, machine)
[235]588    checkpoint.checkpoint('Got infodict')
589    return templates.info(searchList=[d])
[205]590
[632]591def unauthFront(_, _2, _3, fields):
[510]592    """Information for unauth'd users."""
593    return templates.unauth(searchList=[{'simple' : True}])
594
[632]595def overlord(username, state, path, fields):
[633]596    if path == '':
597        return ({'Status': '303 See Other',
598                 'Location': 'overlord/'},
599                "You shouldn't see this message.")
[632]600    if not username in getAfsGroupMembers('system:xvm', 'athena.mit.edu'):
601        raise InvalidInput('username', username, 'Not an overlord.')
602    newstate = State(username, overlord=True)
603    newstate.environ = state.environ
604    return handler(username, newstate, path, fields)
605
606def throwError(_, __, ___, ____):
[598]607    """Throw an error, to test the error-tracing mechanisms."""
[602]608    raise RuntimeError("test of the emergency broadcast system")
[598]609
[113]610mapping = dict(list=listVms,
611               vnc=vnc,
[133]612               command=command,
613               modify=modify,
[113]614               info=info,
[139]615               create=create,
[510]616               help=helpHandler,
[598]617               unauth=unauthFront,
[632]618               overlord=overlord,
[598]619               errortest=throwError)
[113]620
[205]621def printHeaders(headers):
[438]622    """Print a dictionary as HTTP headers."""
[205]623    for key, value in headers.iteritems():
624        print '%s: %s' % (key, value)
625    print
626
[598]627def send_error_mail(subject, body):
628    import subprocess
[205]629
[598]630    to = 'xvm@mit.edu'
631    mail = """To: %s
632From: root@xvm.mit.edu
633Subject: %s
634
635%s
636""" % (to, subject, body)
637    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
638    p.stdin.write(mail)
639    p.stdin.close()
640    p.wait()
641
[603]642def show_error(op, username, fields, err, emsg, traceback):
643    """Print an error page when an exception occurs"""
644    d = dict(op=op, user=username, fields=fields,
645             errorMessage=str(err), stderr=emsg, traceback=traceback)
646    details = templates.error_raw(searchList=[d])
[627]647    if username not in ('price', 'ecprice', 'andersk'): #add yourself at will
648        send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
649                        details)
[603]650    d['details'] = details
651    return templates.error(searchList=[d])
652
[572]653def getUser(environ):
[205]654    """Return the current user based on the SSL environment variables"""
[629]655    return environ.get('REMOTE_USER', None)
[205]656
[632]657def handler(username, state, path, fields):
658    operation, path = pathSplit(path)
659    if not operation:
660        operation = 'list'
661    print 'Starting', operation
662    fun = mapping.get(operation, badOperation)
663    return fun(username, state, path, fields)
664
[579]665class App:
666    def __init__(self, environ, start_response):
667        self.environ = environ
668        self.start = start_response
[205]669
[579]670        self.username = getUser(environ)
671        self.state = State(self.username)
[581]672        self.state.environ = environ
[205]673
[634]674        random.seed() #sigh
675
[579]676    def __iter__(self):
[632]677        start_time = time.time()
[613]678        sipb_xen_database.clear_cache()
[600]679        sys.stderr = StringIO()
[579]680        fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
681        operation = self.environ.get('PATH_INFO', '')
682        if not operation:
[633]683            self.start("301 Moved Permanently", [('Location', './')])
[579]684            return
685        if self.username is None:
686            operation = 'unauth'
687
688        try:
689            checkpoint.checkpoint('Before')
[632]690            output = handler(self.username, self.state, operation, fields)
[579]691            checkpoint.checkpoint('After')
692
693            headers = dict(DEFAULT_HEADERS)
694            if isinstance(output, tuple):
695                new_headers, output = output
696                headers.update(new_headers)
697            e = revertStandardError()
698            if e:
[693]699                if hasattr(output, 'addError'):
700                    output.addError(e)
701                else:
702                    # This only happens on redirects, so it'd be a pain to get
703                    # the message to the user.  Maybe in the response is useful.
704                    output = output + '\n\nstderr:\n' + e
[579]705            output_string =  str(output)
706            checkpoint.checkpoint('output as a string')
707        except Exception, err:
708            if not fields.has_key('js'):
709                if isinstance(err, InvalidInput):
710                    self.start('200 OK', [('Content-Type', 'text/html')])
711                    e = revertStandardError()
[603]712                    yield str(invalidInput(operation, self.username, fields,
713                                           err, e))
[579]714                    return
[602]715            import traceback
716            self.start('500 Internal Server Error',
717                       [('Content-Type', 'text/html')])
718            e = revertStandardError()
[603]719            s = show_error(operation, self.username, fields,
[602]720                           err, e, traceback.format_exc())
721            yield str(s)
722            return
[587]723        status = headers.setdefault('Status', '200 OK')
724        del headers['Status']
725        self.start(status, headers.items())
[579]726        yield output_string
[535]727        if fields.has_key('timedebug'):
[579]728            yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
[209]729
[579]730def constructor():
731    connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
732    return App
[535]733
[579]734def main():
735    from flup.server.fcgi_fork import WSGIServer
736    WSGIServer(constructor()).run()
[535]737
[579]738if __name__ == '__main__':
739    main()
Note: See TracBrowser for help on using the repository browser.