Ignore:
Timestamp:
Oct 20, 2007, 8:28:32 AM (17 years ago)
Author:
ecprice
Message:

A monster checkin, with a variety of changes to the web
infrastructure.

Adds some support for javascript and asynchronous updates.

Also added prototype.

The interface is *really* *slow*, though.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/web/templates/main.py

    r203 r205  
    11#!/usr/bin/python
    2 
    3 import sys
     2"""Main CGI script for web interface"""
     3
     4import base64
     5import cPickle
    46import cgi
     7import datetime
     8import getafsgroups
     9import hmac
    510import os
     11import random
     12import re
     13import sha
     14import simplejson
    615import string
    716import subprocess
    8 import re
     17import sys
    918import time
    10 import cPickle
    11 import base64
    12 import sha
    13 import hmac
    14 import datetime
    15 import StringIO
    16 import getafsgroups
    17 
    18 errio = StringIO.StringIO()
    19 sys.stderr = errio
     19from StringIO import StringIO
     20
     21
     22def revertStandardError():
     23    """Move stderr to stdout, and return the contents of the old stderr."""
     24    errio = sys.stderr
     25    if not isinstance(errio, StringIO):
     26        return None
     27    sys.stderr = sys.stdout
     28    errio.seek(0)
     29    return errio.read()
     30
     31def printError():
     32    """Revert stderr to stdout, and print the contents of stderr"""
     33    if isinstance(sys.stderr, StringIO):
     34        print revertStandardError()
     35
     36if __name__ == '__main__':
     37    import atexit
     38    atexit.register(printError)
     39    sys.stderr = StringIO()
     40
    2041sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
    2142
    2243from Cheetah.Template import Template
    2344from sipb_xen_database import *
    24 import random
    2545
    2646class MyException(Exception):
     
    4464    pass
    4565
     66def helppopup(subj):
     67    """Return HTML code for a (?) link to a specified help topic"""
     68    return ('<span class="helplink"><a href="help?subject=' + subj +
     69            '&amp;simple=true" target="_blank" ' +
     70            'onclick="return helppopup(\'' + subj + '\')">(?)</a></span>')
     71
    4672class Global(object):
     73    """Global state of the system, to avoid duplicate remctls to get state"""
    4774    def __init__(self, user):
    4875        self.user = user
     
    5481    uptimes = property(__get_uptimes)
    5582
     83    def clear(self):
     84        """Clear the state so future accesses reload it."""
     85        for attr in ('_uptimes', ):
     86            if hasattr(self, attr):
     87                delattr(self, attr)
     88
    5689g = None
    5790
    58 def helppopup(subj):
    59     """Return HTML code for a (?) link to a specified help topic"""
    60     return '<span class="helplink"><a href="help?subject='+subj+'&amp;simple=true" target="_blank" onclick="return helppopup(\''+subj+'\')">(?)</a></span>'
    61 
    62 
    63 global_dict = {}
    64 global_dict['helppopup'] = helppopup
    65 
     91class User:
     92    """User class (sort of useless, I admit)"""
     93    def __init__(self, username, email):
     94        self.username = username
     95        self.email = email
     96
     97def makeErrorPre(old, addition):
     98    if addition is None:
     99        return
     100    if old:
     101        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
     102    else:
     103        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
     104
     105Template.helppopup = staticmethod(helppopup)
     106Template.err = None
     107
     108class JsonDict:
     109    """Class to store a dictionary that will be converted to JSON"""
     110    def __init__(self, **kws):
     111        self.data = kws
     112        if 'err' in kws:
     113            err = kws['err']
     114            del kws['err']
     115            self.addError(err)
     116
     117    def __str__(self):
     118        return simplejson.dumps(self.data)
     119
     120    def addError(self, text):
     121        """Add stderr text to be displayed on the website."""
     122        self.data['err'] = \
     123            makeErrorPre(self.data.get('err'), text)
     124
     125class Defaults:
     126    """Class to store default values for fields."""
     127    memory = 256
     128    disk = 4.0
     129    cdrom = ''
     130    name = ''
     131    vmtype = 'hvm'
     132    def __init__(self, max_memory=None, max_disk=None, **kws):
     133        if max_memory is not None:
     134            self.memory = min(self.memory, max_memory)
     135        if max_disk is not None:
     136            self.max_disk = min(self.disk, max_disk)
     137        for key in kws:
     138            setattr(self, key, kws[key])
     139
     140
     141
     142default_headers = {'Content-Type': 'text/html'}
    66143
    67144# ... and stolen from xend/uuid.py
     
    120197    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
    121198
    122 def canAddVm(user):
     199def cantAddVm(user):
    123200    machines = getMachinesByOwner(user)
    124201    active_machines = [x for x in machines if g.uptimes[x]]
    125     return (len(machines) < MAX_VMS_TOTAL and
    126             len(active_machines) < MAX_VMS_ACTIVE)
     202    if len(machines) >= MAX_VMS_TOTAL:
     203        return 'You have too many VMs to create a new one.'
     204    if len(active_machines) >= MAX_VMS_ACTIVE:
     205        return ('You already have the maximum number of VMs turned on.  '
     206                'To create more, turn one off.')
     207    return False
    127208
    128209def haveAccess(user, machine):
     
    132213    if user.username in (machine.administrator, machine.owner):
    133214        return True
    134     if getafsgroups.checkAfsGroup(user.username, machine.administrator, 'athena.mit.edu'): #XXX Cell?
     215    if getafsgroups.checkAfsGroup(user.username, machine.administrator,
     216                                  'athena.mit.edu'): #XXX Cell?
    135217        return True
    136218    if getafsgroups.checkLockerOwner(user.username, machine.owner):
     
    148230    d = dict(op=op, user=user, errorMessage=str(err),
    149231             stderr=emsg)
    150     return Template(file='error.tmpl', searchList=[d, global_dict]);
     232    return Template(file='error.tmpl', searchList=[d]);
    151233
    152234def invalidInput(op, user, fields, err, emsg):
     
    155237             err_value=str(err.err_value), stderr=emsg,
    156238             errorMessage=str(err))
    157     return Template(file='invalid.tmpl', searchList=[d, global_dict]);
     239    return Template(file='invalid.tmpl', searchList=[d]);
    158240
    159241def validMachineName(name):
     
    194276                         stdout=subprocess.PIPE,
    195277                         stderr=subprocess.PIPE)
     278    v = p.wait()
    196279    if kws.get('err'):
    197         p.wait()
    198280        return p.stdout.read(), p.stderr.read()
    199     if p.wait():
    200         print >> sys.stderr, 'Error on remctl', args, ':'
     281    if v:
     282        print >> sys.stderr, 'Error', v, 'on remctl', args, ':'
    201283        print >> sys.stderr, p.stderr.read()
    202284        raise CodeError('ERROR on remctl')
     
    277359    Gets and parses xm list --long
    278360    """
    279     value_string, err_string = remctl('control', machine.name, 'list-long', err=True)
     361    value_string, err_string = remctl('control', machine.name, 'list-long',
     362                                      err=True)
    280363    if 'Unknown command' in err_string:
    281         raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
     364        raise CodeError("ERROR in remctl list-long %s is not registered" %
     365                        (machine.name,))
    282366    elif 'does not exist' in err_string:
    283367        return None
    284368    elif err_string:
    285         raise CodeError("ERROR in remctl list-long %s:  %s" % (machine.name, err_string))
     369        raise CodeError("ERROR in remctl list-long %s:  %s" %
     370                        (machine.name, err_string))
    286371    status = parseStatus(value_string)
    287372    return status
     
    308393            raise InvalidInput('disk', disk,
    309394                               "Max %s" % maxDisk(user))
    310         if not canAddVm(user):
    311             raise InvalidInput('create', True, 'Unable to create more VMs')
    312         res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
     395        reason = cantAddVm(user)
     396        if reason:
     397            raise InvalidInput('create', True, reason)
     398        res = meta.engine.execute('select nextval('
     399                                  '\'"machines_machine_id_seq"\')')
    313400        id = res.fetchone()[0]
    314401        machine = Machine()
     
    326413        disk = Disk(machine.machine_id,
    327414                    'hda', disk)
    328         open = NIC.select_by(machine_id=None)
    329         if not open: #No IPs left!
    330             raise CodeError("No IP addresses left!  Contact sipb-xen-dev@mit.edu")
    331         nic = open[0]
     415        open_nics = NIC.select_by(machine_id=None)
     416        if not open_nics: #No IPs left!
     417            raise CodeError("No IP addresses left!  "
     418                            "Contact sipb-xen-dev@mit.edu")
     419        nic = open_nics[0]
    332420        nic.machine_id = machine.machine_id
    333421        nic.hostname = name
     
    378466    return disk
    379467
    380 def create(user, fields):
    381     """Handler for create requests."""
     468def parseCreate(user, fields):
    382469    name = fields.getfirst('name')
    383470    if not validMachineName(name):
    384         raise InvalidInput('name', name)
     471        raise InvalidInput('name', name, 'You must provide a machine name.')
    385472    name = name.lower()
    386473
    387474    if Machine.get_by(name=name):
    388475        raise InvalidInput('name', name,
    389                            "Already exists")
     476                           "Name already exists.")
    390477   
    391478    memory = fields.getfirst('memory')
     
    402489    cdrom = fields.getfirst('cdrom')
    403490    if cdrom is not None and not CDROM.get(cdrom):
    404         raise CodeError("Invalid cdrom type '%s'" % cdrom)   
    405    
    406     machine = createVm(user, name, memory, disk, is_hvm, cdrom)
    407     d = dict(user=user,
    408              machine=machine)
    409     return Template(file='create.tmpl',
    410                    searchList=[d, global_dict]);
    411 
    412 def listVms(user, fields):
    413     """Handler for list requests."""
     491        raise CodeError("Invalid cdrom type '%s'" % cdrom)
     492    return dict(user=user, name=name, memory=memory, disk=disk,
     493                is_hvm=is_hvm, cdrom=cdrom)
     494
     495def create(user, fields):
     496    """Handler for create requests."""
     497    js = fields.getfirst('js')
     498    try:
     499        parsed_fields = parseCreate(user, fields)
     500        machine = createVm(**parsed_fields)
     501    except InvalidInput, err:
     502        if not js:
     503            raise
     504    else:
     505        err = None
     506        if not js:
     507            d = dict(user=user,
     508                     machine=machine)
     509            return Template(file='create.tmpl', searchList=[d])
     510    g.clear() #Changed global state
     511    d = getListDict(user)
     512    d['err'] = err
     513    if err:
     514        for field in fields.keys():
     515            setattr(d['defaults'], field, fields.getfirst(field))
     516    else:
     517        d['new_machine'] = parsed_fields['name']
     518    t = Template(file='list.tmpl', searchList=[d])
     519    return JsonDict(createtable=t.createTable(),
     520                    machinelist=t.machineList(d['machines']))
     521
     522
     523def getListDict(user):
    414524    machines = [m for m in Machine.select() if haveAccess(user, m)]   
    415525    on = {}
     
    417527    on = g.uptimes
    418528    for m in machines:
     529        m.uptime = g.uptimes.get(m)
    419530        if not on[m]:
    420531            has_vnc[m] = 'Off'
     
    427538    #         on[m.name] = status is not None
    428539    #         has_vnc[m.name] = hasVnc(status)
    429     max_mem=maxMemory(user)
    430     max_disk=maxDisk(user)
     540    max_memory = maxMemory(user)
     541    max_disk = maxDisk(user)
     542    defaults = Defaults(max_memory=max_memory,
     543                        max_disk=max_disk,
     544                        cdrom='gutsy-i386')
    431545    d = dict(user=user,
    432              can_add_vm=canAddVm(user),
    433              max_mem=max_mem,
     546             cant_add_vm=cantAddVm(user),
     547             max_memory=max_memory,
    434548             max_disk=max_disk,
    435              default_mem=max_mem,
    436              default_disk=min(4.0, max_disk),
     549             defaults=defaults,
    437550             machines=machines,
    438551             has_vnc=has_vnc,
    439552             uptimes=g.uptimes,
    440553             cdroms=CDROM.select())
    441     return Template(file='list.tmpl', searchList=[d, global_dict])
    442 
     554    return d
     555
     556def listVms(user, fields):
     557    """Handler for list requests."""
     558    d = getListDict(user)
     559    t = Template(file='list.tmpl', searchList=[d])
     560    js = fields.getfirst('js')
     561    if not js:
     562        return t
     563    if js == 'machinelist':
     564        return t.machineList(d['machines'])
     565    elif js.startswith('machinerow-'):
     566        request_machine_id = int(js.split('-')[1])
     567        m = [x for x in d['machines'] if x.id == request_machine_id]
     568        return t.machineRow(m)
     569    elif js == 'createtable':
     570        return t.createTable()
     571           
    443572def testMachineId(user, machineId, exists=True):
    444573    """Parse, validate and check authorization for a given machineId.
     
    469598    You might want iptables like:
    470599
    471     -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
    472     -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp --dport 10003 -j SNAT --to-source 18.187.7.142
    473     -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
     600    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
     601      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
     602    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
     603      --dport 10003 -j SNAT --to-source 18.187.7.142
     604    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
     605      --dport 10003 -j ACCEPT
    474606
    475607    Remember to enable iptables!
     
    482614    data = {}
    483615    data["user"] = user.username
    484     data["machine"]=machine.name
    485     data["expires"]=time.time()+(5*60)
    486     pickledData = cPickle.dumps(data)
     616    data["machine"] = machine.name
     617    data["expires"] = time.time()+(5*60)
     618    pickled_data = cPickle.dumps(data)
    487619    m = hmac.new(TOKEN_KEY, digestmod=sha)
    488     m.update(pickledData)
    489     token = {'data': pickledData, 'digest': m.digest()}
     620    m.update(pickled_data)
     621    token = {'data': pickled_data, 'digest': m.digest()}
    490622    token = cPickle.dumps(token)
    491623    token = base64.urlsafe_b64encode(token)
     
    500632             hostname=os.environ.get('SERVER_NAME', 'localhost'),
    501633             authtoken=token)
    502     return Template(file='vnc.tmpl',
    503                    searchList=[d, global_dict])
     634    return Template(file='vnc.tmpl', searchList=[d])
    504635
    505636def getNicInfo(data_dict, machine):
     
    517648    for i in range(len(machine.nics)):
    518649        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
    519         data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
     650        data_dict['nic%s_hostname' % i] = (machine.nics[i].hostname +
     651                                           '.servers.csail.mit.edu')
    520652        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
    521653        data_dict['nic%s_ip' % i] = machine.nics[i].ip
     
    535667    for disk in machine.disks:
    536668        name = disk.guest_device_name
    537         disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
     669        disk_fields.extend([(x % name, y % name) for x, y in
     670                            disk_fields_template])
    538671        data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
    539672    return disk_fields
     
    543676    remctl('control', machine.name, 'destroy', err=True)
    544677    transaction = ctx.current.create_transaction()
    545     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
     678    delete_disk_pairs = [(machine.name, d.guest_device_name)
     679                         for d in machine.disks]
    546680    try:
    547681        for nic in machine.nics:
     
    560694    unregisterMachine(machine)
    561695
    562 def command(user, fields):
    563     """Handler for running commands like boot and delete on a VM."""
     696def commandResult(user, fields):
    564697    print >> sys.stderr, time.time()-start_time
    565698    machine = testMachineId(user, fields.getfirst('machine_id'))
     
    569702    if cdrom is not None and not CDROM.get(cdrom):
    570703        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
    571     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
     704    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown',
     705                      'Delete VM'):
    572706        raise CodeError("Invalid action '%s'" % action)
    573707    if action == 'Reboot':
    574708        if cdrom is not None:
    575             remctl('control', machine.name, 'reboot', cdrom)
     709            out, err = remctl('control', machine.name, 'reboot', cdrom,
     710                              err=True)
    576711        else:
    577             remctl('control', machine.name, 'reboot')
     712            out, err = remctl('control', machine.name, 'reboot',
     713                              err=True)
     714        if err:
     715            if re.match("Error: Domain '.*' does not exist.", err):
     716                raise InvalidInput("action", "reboot",
     717                                   "Machine is not on")
     718            else:
     719                print >> sys.stderr, 'Error on reboot:'
     720                print >> sys.stderr, err
     721                raise CodeError('ERROR on remctl')
     722               
    578723    elif action == 'Power on':
    579724        if maxMemory(user) < machine.memory:
    580725            raise InvalidInput('action', 'Power on',
    581                                "You don't have enough free RAM quota to turn on this machine")
     726                               "You don't have enough free RAM quota "
     727                               "to turn on this machine.")
    582728        bootMachine(machine, cdrom)
    583729    elif action == 'Power off':
    584         remctl('control', machine.name, 'destroy')
     730        out, err = remctl('control', machine.name, 'destroy', err=True)
     731        if err:
     732            if re.match("Error: Domain '.*' does not exist.", err):
     733                raise InvalidInput("action", "Power off",
     734                                   "Machine is not on.")
     735            else:
     736                print >> sys.stderr, 'Error on power off:'
     737                print >> sys.stderr, err
     738                raise CodeError('ERROR on remctl')
    585739    elif action == 'Shutdown':
    586         remctl('control', machine.name, 'shutdown')
     740        out, err = remctl('control', machine.name, 'shutdown', err=True)
     741        if err:
     742            if re.match("Error: Domain '.*' does not exist.", err):
     743                raise InvalidInput("action", "Shutdown",
     744                                   "Machine is not on.")
     745            else:
     746                print >> sys.stderr, 'Error on Shutdown:'
     747                print >> sys.stderr, err
     748                raise CodeError('ERROR on remctl')
    587749    elif action == 'Delete VM':
    588750        deleteVM(machine)
     
    592754             command=action,
    593755             machine=machine)
    594     return Template(file="command.tmpl", searchList=[d, global_dict])
     756    return d
     757
     758def command(user, fields):
     759    """Handler for running commands like boot and delete on a VM."""
     760    js = fields.getfirst('js')
     761    try:
     762        d = commandResult(user, fields)
     763    except InvalidInput, err:
     764        if not js:
     765            raise
     766        result = None
     767    else:
     768        err = None
     769        result = 'Success!'
     770        if not js:
     771            return Template(file='command.tmpl', searchList=[d])
     772    if js == 'list':
     773        g.clear() #Changed global state
     774        d = getListDict(user)
     775        t = Template(file='list.tmpl', searchList=[d])
     776        return JsonDict(createtable=t.createTable(),
     777                        machinelist=t.machineList(d['machines']),
     778                        result=result,
     779                        err=err)
     780    elif js == 'info':
     781        machine = testMachineId(user, fields.getfirst('machine_id'))
     782        d = infoDict(user, machine)
     783        t = Template(file='info.tmpl', searchList=[d])
     784        return JsonDict(info=t.infoTable(),
     785                        commands=t.commands(),
     786                        modify=t.modifyForm(),
     787                        result=result,
     788                        err=err)
     789    else:
     790        raise InvalidInput('js', js, 'Not a known js type.')
    595791
    596792def testAdmin(user, admin, machine):
     
    601797    if getafsgroups.checkAfsGroup(user.username, admin, 'athena.mit.edu'):
    602798        return admin
    603     if getafsgroups.checkAfsGroup(user.username, 'system:'+admin, 'athena.mit.edu'):
     799    if getafsgroups.checkAfsGroup(user.username, 'system:'+admin,
     800                                  'athena.mit.edu'):
    604801        return 'system:'+admin
    605     raise InvalidInput('admin', admin,
    606                        'You must control the group you move it to')
     802    raise InvalidInput('administrator', admin,
     803                       'You must control the group you move it to.')
    607804   
    608805def testOwner(user, owner, machine):
    609806    if owner in (None, machine.owner):
    610807        return None
    611     #XXX should you be able to transfer ownership if you don't already own it?
    612     #if not owns(user, machine):
    613     #    raise InvalidInput('owner', owner, "You don't own this machine, so you can't  transfer ownership")
    614808    value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
    615809    if value == True:
     
    621815        return None
    622816    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
    623         raise InvalidInput('contact', contact, "Not a valid email")
     817        raise InvalidInput('contact', contact, "Not a valid email.")
    624818    return contact
    625819
     
    632826    if not Machine.select_by(name=name):
    633827        return name
    634     raise InvalidInput('name', name, "Already taken")
     828    raise InvalidInput('name', name, "Name is already taken.")
    635829
    636830def testHostname(user, hostname, machine):
     
    643837                           "Already exists")
    644838    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
    645         raise InvalidInput('hostname', hostname, "Not a valid hostname; must only use number, letters, and dashes.")
     839        raise InvalidInput('hostname', hostname, "Not a valid hostname; "
     840                           "must only use number, letters, and dashes.")
    646841    return hostname
    647842
    648 def modify(user, fields):
    649     """Handler for modifying attributes of a machine."""
    650 
     843def modifyDict(user, fields):
    651844    olddisk = {}
    652845    transaction = ctx.current.create_transaction()
     
    659852        name = testName(user, fields.getfirst('name'), machine)
    660853        oldname = machine.name
    661         command="modify"
     854        command = "modify"
    662855
    663856        memory = fields.getfirst('memory')
     
    675868                ctx.current.save(disk)
    676869       
    677         # XXX first NIC gets hostname on change?  Interface doesn't support more.
     870        # XXX first NIC gets hostname on change? 
     871        # Interface doesn't support more.
    678872        for nic in machine.nics[:1]:
    679873            nic.hostname = hostname
     
    700894            remctl("web", "lvrename", oldname, disk.guest_device_name, name)
    701895        remctl("web", "moveregister", oldname, name)
    702     d = dict(user=user,
    703              command=command,
    704              machine=machine)
    705     return Template(file="command.tmpl", searchList=[d, global_dict])   
    706 
    707 
    708 def help(user, fields):
     896    return dict(user=user,
     897                command=command,
     898                machine=machine)
     899   
     900def modify(user, fields):
     901    """Handler for modifying attributes of a machine."""
     902    js = fields.getfirst('js')
     903    try:
     904        modify_dict = modifyDict(user, fields)
     905    except InvalidInput, err:
     906        if not js:
     907            raise
     908        result = ''
     909        machine = testMachineId(user, fields.getfirst('machine_id'))
     910    else:
     911        machine = modify_dict['machine']
     912        result='Success!'
     913        err = None
     914        if not js:
     915            return Template(file='command.tmpl', searchList=[modify_dict])
     916    info_dict = infoDict(user, machine)
     917    info_dict['err'] = err
     918    if err:
     919        for field in fields.keys():
     920            setattr(info_dict['defaults'], field, fields.getfirst(field))
     921    t = Template(file='info.tmpl', searchList=[info_dict])
     922    return JsonDict(info=t.infoTable(),
     923                    commands=t.commands(),
     924                    modify=t.modifyForm(),
     925                    result=result,
     926                    err=err)
     927   
     928
     929def helpHandler(user, fields):
    709930    """Handler for help messages."""
    710931    simple = fields.getfirst('simple')
    711932    subjects = fields.getlist('subject')
    712933   
    713     mapping = dict(paravm_console="""
     934    help_mapping = dict(paravm_console="""
    714935ParaVM machines do not support console access over VNC.  To access
    715936these machines, you either need to boot with a liveCD and ssh in or
    716937hope that the sipb-xen maintainers add support for serial consoles.""",
    717                    hvm_paravm="""
     938                        hvm_paravm="""
    718939HVM machines use the virtualization features of the processor, while
    719940ParaVM machines use Xen's emulation of virtualization features.  You
    720941want an HVM virtualized machine.""",
    721                    cpu_weight="""Don't ask us!  We're as mystified as you are.""",
    722                    owner="""The owner field is used to determine <a href="help?subject=quotas">quotas</a>.  It must be the name
    723 of a locker that you are an AFS administrator of.  In particular, you
    724 or an AFS group you are a member of must have AFS rlidwka bits on the
     942                        cpu_weight="""
     943Don't ask us!  We're as mystified as you are.""",
     944                        owner="""
     945The owner field is used to determine <a
     946href="help?subject=quotas">quotas</a>.  It must be the name of a
     947locker that you are an AFS administrator of.  In particular, you or an
     948AFS group you are a member of must have AFS rlidwka bits on the
    725949locker.  You can check see who administers the LOCKER locker using the
    726 command 'fs la /mit/LOCKER' on Athena.)  See also <a href="help?subject=administrator">administrator</a>.""",
    727                    administrator="""The administrator field determines who can access the console and power on and off the machine.  This can be either a user or a moira group.""",
    728                    quotas="""Quotas are determined on a per-locker basis.  Each
    729 quota may have a maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4 active machines."""
    730 
     950command 'fs la /mit/LOCKER' on Athena.)  See also <a
     951href="help?subject=administrator">administrator</a>.""",
     952                        administrator="""
     953The administrator field determines who can access the console and
     954power on and off the machine.  This can be either a user or a moira
     955group.""",
     956                        quotas="""
     957Quotas are determined on a per-locker basis.  Each quota may have a
     958maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
     959active machines."""
    731960                   )
    732961   
    733962    if not subjects:
    734         subjects = sorted(mapping.keys())
     963        subjects = sorted(help_mapping.keys())
    735964       
    736965    d = dict(user=user,
    737966             simple=simple,
    738967             subjects=subjects,
    739              mapping=mapping)
    740    
    741     return Template(file="help.tmpl", searchList=[d, global_dict])
    742    
    743 
    744 def info(user, fields):
    745     """Handler for info on a single VM."""
    746     machine = testMachineId(user, fields.getfirst('machine_id'))
     968             mapping=help_mapping)
     969   
     970    return Template(file="help.tmpl", searchList=[d])
     971   
     972
     973def badOperation(u, e):
     974    raise CodeError("Unknown operation")
     975
     976def infoDict(user, machine):
    747977    status = statusInfo(machine)
    748978    has_vnc = hasVnc(status)
     
    750980        main_status = dict(name=machine.name,
    751981                           memory=str(machine.memory))
    752         uptime=None
    753         cputime=None
     982        uptime = None
     983        cputime = None
    754984    else:
    755985        main_status = dict(status[1:])
     
    7891019    nic_fields = getNicInfo(machine_info, machine)
    7901020    nic_point = display_fields.index('NIC_INFO')
    791     display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
     1021    display_fields = (display_fields[:nic_point] + nic_fields +
     1022                      display_fields[nic_point+1:])
    7921023
    7931024    disk_fields = getDiskInfo(machine_info, machine)
    7941025    disk_point = display_fields.index('DISK_INFO')
    795     display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
     1026    display_fields = (display_fields[:disk_point] + disk_fields +
     1027                      display_fields[disk_point+1:])
    7961028   
    7971029    main_status['memory'] += ' MB'
     
    8081040    max_mem = maxMemory(user, machine)
    8091041    max_disk = maxDisk(user, machine)
     1042    defaults=Defaults()
     1043    for name in 'machine_id name administrator owner memory contact'.split():
     1044        setattr(defaults, name, getattr(machine, name))
     1045    if machine.nics:
     1046        defaults.hostname = machine.nics[0].hostname
     1047    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
    8101048    d = dict(user=user,
    8111049             cdroms=CDROM.select(),
    8121050             on=status is not None,
    8131051             machine=machine,
     1052             defaults=defaults,
    8141053             has_vnc=has_vnc,
    8151054             uptime=str(uptime),
     
    8191058             owner_help=helppopup("owner"),
    8201059             fields = fields)
    821     return Template(file='info.tmpl',
    822                    searchList=[d, global_dict])
     1060    return d
     1061
     1062def info(user, fields):
     1063    """Handler for info on a single VM."""
     1064    machine = testMachineId(user, fields.getfirst('machine_id'))
     1065    d = infoDict(user, machine)
     1066    return Template(file='info.tmpl', searchList=[d])
    8231067
    8241068mapping = dict(list=listVms,
     
    8281072               info=info,
    8291073               create=create,
    830                help=help)
     1074               help=helpHandler)
     1075
     1076def printHeaders(headers):
     1077    for key, value in headers.iteritems():
     1078        print '%s: %s' % (key, value)
     1079    print
     1080
     1081
     1082def getUser():
     1083    """Return the current user based on the SSL environment variables"""
     1084    if 'SSL_CLIENT_S_DN_Email' in os.environ:
     1085        username = os.environ['SSL_CLIENT_S_DN_Email'].split("@")[0]
     1086        return User(username, os.environ['SSL_CLIENT_S_DN_Email'])
     1087    else:
     1088        return User('moo', 'nobody')
    8311089
    8321090if __name__ == '__main__':
    8331091    start_time = time.time()
    8341092    fields = cgi.FieldStorage()
    835     class User:
    836         username = "moo"
    837         email = 'moo@cow.com'
    838     u = User()
     1093    u = getUser()
    8391094    g = Global(u)
    840     if 'SSL_CLIENT_S_DN_Email' in os.environ:
    841         username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
    842         u.username = username
    843         u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
    844     else:
    845         u.username = 'moo'
    846         u.email = 'nobody'
    847     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
    8481095    operation = os.environ.get('PATH_INFO', '')
    8491096    if not operation:
     
    8571104        operation = 'list'
    8581105
    859     def badOperation(u, e):
    860         raise CodeError("Unknown operation")
     1106
    8611107
    8621108    fun = mapping.get(operation, badOperation)
    863     if fun not in (help, ):
    864         connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     1109
     1110    if fun not in (helpHandler, ):
     1111        connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
    8651112    try:
    8661113        output = fun(u, fields)
    867         print 'Content-Type: text/html\n'
    868         sys.stderr=sys.stdout
    869         errio.seek(0)
    870         e = errio.read()
     1114
     1115        headers = dict(default_headers)
     1116        if isinstance(output, tuple):
     1117            new_headers, output = output
     1118            headers.update(new_headers)
     1119
     1120        e = revertStandardError()
    8711121        if e:
    872             output = str(output)
    873             output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
     1122            output.addError(e)
     1123        printHeaders(headers)
    8741124        print output
    875     except CodeError, err:
    876         print 'Content-Type: text/html\n'
    877         sys.stderr=sys.stdout
    878         errio.seek(0)
    879         e = errio.read()
    880         print error(operation, u, fields, err, e)
    881     except InvalidInput, err:
    882         print 'Content-Type: text/html\n'
    883         sys.stderr=sys.stdout
    884         errio.seek(0)
    885         e = errio.read()
    886         print invalidInput(operation, u, fields, err, e)
    887     except:
     1125    except Exception, err:
     1126        if not fields.has_key('js'):
     1127            if isinstance(err, CodeError):
     1128                print 'Content-Type: text/html\n'
     1129                e = revertStandardError()
     1130                print error(operation, u, fields, err, e)
     1131                sys.exit(1)
     1132            if isinstance(err, InvalidInput):
     1133                print 'Content-Type: text/html\n'
     1134                e = revertStandardError()
     1135                print invalidInput(operation, u, fields, err, e)
     1136                sys.exit(1)
    8881137        print 'Content-Type: text/plain\n'
    889         sys.stderr=sys.stdout
    890         errio.seek(0)
    891         e = errio.read()
     1138        print 'Uh-oh!  We experienced an error.'
     1139        print 'Please email sipb-xen@mit.edu with the contents of this page.'
     1140        print '----'
     1141        e = revertStandardError()
    8921142        print e
    8931143        print '----'
Note: See TracChangeset for help on using the changeset viewer.