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

Last change on this file since 829 was 799, checked in by geofft, 16 years ago

oops

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