source: package_branches/invirt-web/cherrypy-rebased/code/main.py @ 2677

Last change on this file since 2677 was 2677, checked in by broder, 14 years ago

Get username from cherrypy request object

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