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

Last change on this file since 432 was 432, checked in by ecprice, 16 years ago

Someone forgot to commit their additions to the help text.

  • Property svn:executable set to *
File size: 22.5 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
[113]9import os
[205]10import sha
11import simplejson
12import sys
[118]13import time
[205]14from StringIO import StringIO
[113]15
[205]16def revertStandardError():
17    """Move stderr to stdout, and return the contents of the old stderr."""
18    errio = sys.stderr
19    if not isinstance(errio, StringIO):
20        return None
21    sys.stderr = sys.stdout
22    errio.seek(0)
23    return errio.read()
24
25def printError():
26    """Revert stderr to stdout, and print the contents of stderr"""
27    if isinstance(sys.stderr, StringIO):
28        print revertStandardError()
29
30if __name__ == '__main__':
31    import atexit
32    atexit.register(printError)
33    sys.stderr = StringIO()
34
[113]35sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
36
[235]37import templates
[113]38from Cheetah.Template import Template
[261]39from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess
[209]40import validation
41from webcommon import InvalidInput, CodeError, g
42import controls
[113]43
[235]44class Checkpoint:
45    def __init__(self):
46        self.start_time = time.time()
47        self.checkpoints = []
48
49    def checkpoint(self, s):
50        self.checkpoints.append((s, time.time()))
51
52    def __str__(self):
53        return ('Timing info:\n%s\n' %
54                '\n'.join(['%s: %s' % (d, t - self.start_time) for
55                           (d, t) in self.checkpoints]))
56
57checkpoint = Checkpoint()
58
59
[205]60def helppopup(subj):
61    """Return HTML code for a (?) link to a specified help topic"""
62    return ('<span class="helplink"><a href="help?subject=' + subj + 
63            '&amp;simple=true" target="_blank" ' + 
64            'onclick="return helppopup(\'' + subj + '\')">(?)</a></span>')
65
66def makeErrorPre(old, addition):
67    if addition is None:
68        return
69    if old:
70        return old[:-6]  + '\n----\n' + str(addition) + '</pre>'
71    else:
72        return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
[139]73
[205]74Template.helppopup = staticmethod(helppopup)
75Template.err = None
[139]76
[205]77class JsonDict:
78    """Class to store a dictionary that will be converted to JSON"""
79    def __init__(self, **kws):
80        self.data = kws
81        if 'err' in kws:
82            err = kws['err']
83            del kws['err']
84            self.addError(err)
[139]85
[205]86    def __str__(self):
87        return simplejson.dumps(self.data)
88
89    def addError(self, text):
90        """Add stderr text to be displayed on the website."""
91        self.data['err'] = \
92            makeErrorPre(self.data.get('err'), text)
93
94class Defaults:
95    """Class to store default values for fields."""
96    memory = 256
97    disk = 4.0
98    cdrom = ''
99    name = ''
100    vmtype = 'hvm'
101    def __init__(self, max_memory=None, max_disk=None, **kws):
102        if max_memory is not None:
103            self.memory = min(self.memory, max_memory)
104        if max_disk is not None:
105            self.max_disk = min(self.disk, max_disk)
106        for key in kws:
107            setattr(self, key, kws[key])
108
109
110
[209]111DEFAULT_HEADERS = {'Content-Type': 'text/html'}
[205]112
[153]113def error(op, user, fields, err, emsg):
[145]114    """Print an error page when a CodeError occurs"""
[153]115    d = dict(op=op, user=user, errorMessage=str(err),
116             stderr=emsg)
[235]117    return templates.error(searchList=[d])
[113]118
[153]119def invalidInput(op, user, fields, err, emsg):
120    """Print an error page when an InvalidInput exception occurs"""
121    d = dict(op=op, user=user, err_field=err.err_field,
122             err_value=str(err.err_value), stderr=emsg,
123             errorMessage=str(err))
[235]124    return templates.invalid(searchList=[d])
[153]125
[119]126def hasVnc(status):
[133]127    """Does the machine with a given status list support VNC?"""
[119]128    if status is None:
129        return False
130    for l in status:
131        if l[0] == 'device' and l[1][0] == 'vfb':
132            d = dict(l[1][1:])
133            return 'location' in d
134    return False
135
[205]136def parseCreate(user, fields):
[134]137    name = fields.getfirst('name')
[209]138    if not validation.validMachineName(name):
[429]139        raise InvalidInput('name', name, 'You must provide a machine name.  Max 22 chars, alnum plus \'-\' and \'_\'.')
[162]140    name = name.lower()
[134]141
142    if Machine.get_by(name=name):
[153]143        raise InvalidInput('name', name,
[205]144                           "Name already exists.")
[113]145   
[228]146    owner = validation.testOwner(user, fields.getfirst('owner'))
147
[134]148    memory = fields.getfirst('memory')
[266]149    memory = validation.validMemory(owner, memory, on=True)
[134]150   
[243]151    disk_size = fields.getfirst('disk')
[266]152    disk_size = validation.validDisk(owner, disk_size)
[134]153
[113]154    vm_type = fields.getfirst('vmtype')
155    if vm_type not in ('hvm', 'paravm'):
[145]156        raise CodeError("Invalid vm type '%s'"  % vm_type)   
[113]157    is_hvm = (vm_type == 'hvm')
158
159    cdrom = fields.getfirst('cdrom')
160    if cdrom is not None and not CDROM.get(cdrom):
[205]161        raise CodeError("Invalid cdrom type '%s'" % cdrom)
[340]162
163    clone_from = fields.getfirst('clone_from')
164    if clone_from and clone_from != 'ice3':
165        raise CodeError("Invalid clone image '%s'" % clone_from)
166   
[243]167    return dict(contact=user, name=name, memory=memory, disk_size=disk_size,
[340]168                owner=owner, is_hvm=is_hvm, cdrom=cdrom, clone_from=clone_from)
[113]169
[205]170def create(user, fields):
171    """Handler for create requests."""
172    try:
173        parsed_fields = parseCreate(user, fields)
[209]174        machine = controls.createVm(**parsed_fields)
[205]175    except InvalidInput, err:
[207]176        pass
[205]177    else:
178        err = None
179    g.clear() #Changed global state
180    d = getListDict(user)
181    d['err'] = err
182    if err:
183        for field in fields.keys():
184            setattr(d['defaults'], field, fields.getfirst(field))
185    else:
186        d['new_machine'] = parsed_fields['name']
[235]187    return templates.list(searchList=[d])
[205]188
189
190def getListDict(user):
[261]191    machines = g.machines
[235]192    checkpoint.checkpoint('Got my machines')
[133]193    on = {}
[119]194    has_vnc = {}
[152]195    on = g.uptimes
[235]196    checkpoint.checkpoint('Got uptimes')
[136]197    for m in machines:
[205]198        m.uptime = g.uptimes.get(m)
[144]199        if not on[m]:
200            has_vnc[m] = 'Off'
[138]201        elif m.type.hvm:
[144]202            has_vnc[m] = True
[136]203        else:
[144]204            has_vnc[m] = "ParaVM"+helppopup("paravm_console")
[209]205    max_memory = validation.maxMemory(user)
206    max_disk = validation.maxDisk(user)
[235]207    checkpoint.checkpoint('Got max mem/disk')
[205]208    defaults = Defaults(max_memory=max_memory,
209                        max_disk=max_disk,
[228]210                        owner=user,
[205]211                        cdrom='gutsy-i386')
[235]212    checkpoint.checkpoint('Got defaults')
[424]213    def sortkey(machine):
214        return (machine.owner != user, machine.owner, machine.name)
215    machines = sorted(machines, key=sortkey)
[113]216    d = dict(user=user,
[209]217             cant_add_vm=validation.cantAddVm(user),
[205]218             max_memory=max_memory,
[144]219             max_disk=max_disk,
[205]220             defaults=defaults,
[113]221             machines=machines,
[119]222             has_vnc=has_vnc,
[157]223             uptimes=g.uptimes,
[113]224             cdroms=CDROM.select())
[205]225    return d
[113]226
[205]227def listVms(user, fields):
228    """Handler for list requests."""
[235]229    checkpoint.checkpoint('Getting list dict')
[205]230    d = getListDict(user)
[235]231    checkpoint.checkpoint('Got list dict')
232    return templates.list(searchList=[d])
[205]233           
[113]234def vnc(user, fields):
[119]235    """VNC applet page.
236
237    Note that due to same-domain restrictions, the applet connects to
238    the webserver, which needs to forward those requests to the xen
239    server.  The Xen server runs another proxy that (1) authenticates
240    and (2) finds the correct port for the VM.
241
242    You might want iptables like:
243
[205]244    -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
245      --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
246    -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
247      --dport 10003 -j SNAT --to-source 18.187.7.142
248    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
249      --dport 10003 -j ACCEPT
[145]250
251    Remember to enable iptables!
252    echo 1 > /proc/sys/net/ipv4/ip_forward
[119]253    """
[209]254    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
[118]255   
256    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
257
258    data = {}
[228]259    data["user"] = user
[205]260    data["machine"] = machine.name
261    data["expires"] = time.time()+(5*60)
262    pickled_data = cPickle.dumps(data)
[118]263    m = hmac.new(TOKEN_KEY, digestmod=sha)
[205]264    m.update(pickled_data)
265    token = {'data': pickled_data, 'digest': m.digest()}
[118]266    token = cPickle.dumps(token)
267    token = base64.urlsafe_b64encode(token)
268   
[209]269    status = controls.statusInfo(machine)
[152]270    has_vnc = hasVnc(status)
271   
[113]272    d = dict(user=user,
[152]273             on=status,
274             has_vnc=has_vnc,
[113]275             machine=machine,
[119]276             hostname=os.environ.get('SERVER_NAME', 'localhost'),
[113]277             authtoken=token)
[235]278    return templates.vnc(searchList=[d])
[113]279
[252]280def getHostname(nic):
281    if nic.hostname and '.' in nic.hostname:
282        return nic.hostname
283    elif nic.machine:
284        return nic.machine.name + '.servers.csail.mit.edu'
285    else:
286        return None
287
288
[133]289def getNicInfo(data_dict, machine):
[145]290    """Helper function for info, get data on nics for a machine.
291
292    Modifies data_dict to include the relevant data, and returns a list
293    of (key, name) pairs to display "name: data_dict[key]" to the user.
294    """
[133]295    data_dict['num_nics'] = len(machine.nics)
[227]296    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
[133]297                           ('nic%s_mac', 'NIC %s MAC Addr'),
298                           ('nic%s_ip', 'NIC %s IP'),
299                           ]
300    nic_fields = []
301    for i in range(len(machine.nics)):
302        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
[227]303        if not i:
[252]304            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
[133]305        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
306        data_dict['nic%s_ip' % i] = machine.nics[i].ip
307    if len(machine.nics) == 1:
308        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
309    return nic_fields
310
311def getDiskInfo(data_dict, machine):
[145]312    """Helper function for info, get data on disks for a machine.
313
314    Modifies data_dict to include the relevant data, and returns a list
315    of (key, name) pairs to display "name: data_dict[key]" to the user.
316    """
[133]317    data_dict['num_disks'] = len(machine.disks)
318    disk_fields_template = [('%s_size', '%s size')]
319    disk_fields = []
320    for disk in machine.disks:
321        name = disk.guest_device_name
[205]322        disk_fields.extend([(x % name, y % name) for x, y in 
323                            disk_fields_template])
[211]324        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
[133]325    return disk_fields
326
[205]327def command(user, fields):
328    """Handler for running commands like boot and delete on a VM."""
[207]329    back = fields.getfirst('back')
[205]330    try:
[209]331        d = controls.commandResult(user, fields)
[207]332        if d['command'] == 'Delete VM':
333            back = 'list'
[205]334    except InvalidInput, err:
[207]335        if not back:
[205]336            raise
[261]337        #print >> sys.stderr, err
338        result = err
[205]339    else:
340        result = 'Success!'
[207]341        if not back:
[235]342            return templates.command(searchList=[d])
[207]343    if back == 'list':
[205]344        g.clear() #Changed global state
345        d = getListDict(user)
[207]346        d['result'] = result
[235]347        return templates.list(searchList=[d])
[207]348    elif back == 'info':
[209]349        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
[407]350        return ({'Status': '302',
351                 'Location': '/info?machine_id=%d' % machine.machine_id},
352                "You shouldn't see this message.")
[205]353    else:
[261]354        raise InvalidInput('back', back, 'Not a known back page.')
[205]355
356def modifyDict(user, fields):
[177]357    olddisk = {}
[161]358    transaction = ctx.current.create_transaction()
359    try:
[209]360        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
361        owner = validation.testOwner(user, fields.getfirst('owner'), machine)
362        admin = validation.testAdmin(user, fields.getfirst('administrator'),
363                                     machine)
364        contact = validation.testContact(user, fields.getfirst('contact'),
365                                         machine)
366        name = validation.testName(user, fields.getfirst('name'), machine)
[161]367        oldname = machine.name
[205]368        command = "modify"
[153]369
[161]370        memory = fields.getfirst('memory')
371        if memory is not None:
[209]372            memory = validation.validMemory(user, memory, machine, on=False)
[161]373            machine.memory = memory
[177]374 
[209]375        disksize = validation.testDisk(user, fields.getfirst('disk'))
[161]376        if disksize is not None:
[209]377            disksize = validation.validDisk(user, disksize, machine)
[177]378            disk = machine.disks[0]
379            if disk.size != disksize:
380                olddisk[disk.guest_device_name] = disksize
381                disk.size = disksize
382                ctx.current.save(disk)
[161]383       
[187]384        if owner is not None:
[161]385            machine.owner = owner
[187]386        if name is not None:
[161]387            machine.name = name
[187]388        if admin is not None:
389            machine.administrator = admin
390        if contact is not None:
391            machine.contact = contact
[161]392           
393        ctx.current.save(machine)
394        transaction.commit()
395    except:
396        transaction.rollback()
[163]397        raise
[177]398    for diskname in olddisk:
[209]399        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
[187]400    if name is not None:
[209]401        controls.renameMachine(machine, oldname, name)
[205]402    return dict(user=user,
403                command=command,
404                machine=machine)
405   
406def modify(user, fields):
407    """Handler for modifying attributes of a machine."""
408    try:
409        modify_dict = modifyDict(user, fields)
410    except InvalidInput, err:
[207]411        result = None
[209]412        machine = validation.testMachineId(user, fields.getfirst('machine_id'))
[205]413    else:
414        machine = modify_dict['machine']
[209]415        result = 'Success!'
[205]416        err = None
417    info_dict = infoDict(user, machine)
418    info_dict['err'] = err
419    if err:
420        for field in fields.keys():
421            setattr(info_dict['defaults'], field, fields.getfirst(field))
[207]422    info_dict['result'] = result
[235]423    return templates.info(searchList=[info_dict])
[205]424   
[161]425
[205]426def helpHandler(user, fields):
[145]427    """Handler for help messages."""
[139]428    simple = fields.getfirst('simple')
429    subjects = fields.getlist('subject')
430   
[205]431    help_mapping = dict(paravm_console="""
[432]432ParaVM machines do not support local console access over VNC.  To
433access the serial console of these machines, you can SSH with Kerberos
434to sipb-xen-console.mit.edu, using the name of the machine as your
435username.""",
[205]436                        hvm_paravm="""
[139]437HVM machines use the virtualization features of the processor, while
438ParaVM machines use Xen's emulation of virtualization features.  You
439want an HVM virtualized machine.""",
[205]440                        cpu_weight="""
441Don't ask us!  We're as mystified as you are.""",
442                        owner="""
443The owner field is used to determine <a
444href="help?subject=quotas">quotas</a>.  It must be the name of a
445locker that you are an AFS administrator of.  In particular, you or an
446AFS group you are a member of must have AFS rlidwka bits on the
[432]447locker.  You can check who administers the LOCKER locker using the
448commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
[205]449href="help?subject=administrator">administrator</a>.""",
450                        administrator="""
451The administrator field determines who can access the console and
452power on and off the machine.  This can be either a user or a moira
453group.""",
454                        quotas="""
[408]455Quotas are determined on a per-locker basis.  Each locker may have a
[205]456maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
[309]457active machines.""",
458                        console="""
459<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
460setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
461your machine will run just fine, but the applet's display of the
462console will suffer artifacts.
463"""
[187]464                   )
[139]465   
[187]466    if not subjects:
[205]467        subjects = sorted(help_mapping.keys())
[187]468       
[139]469    d = dict(user=user,
470             simple=simple,
471             subjects=subjects,
[205]472             mapping=help_mapping)
[139]473   
[235]474    return templates.help(searchList=[d])
[139]475   
[133]476
[205]477def badOperation(u, e):
478    raise CodeError("Unknown operation")
479
480def infoDict(user, machine):
[209]481    status = controls.statusInfo(machine)
[235]482    checkpoint.checkpoint('Getting status info')
[133]483    has_vnc = hasVnc(status)
484    if status is None:
485        main_status = dict(name=machine.name,
486                           memory=str(machine.memory))
[205]487        uptime = None
488        cputime = None
[133]489    else:
490        main_status = dict(status[1:])
[167]491        start_time = float(main_status.get('start_time', 0))
492        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
493        cpu_time_float = float(main_status.get('cpu_time', 0))
494        cputime = datetime.timedelta(seconds=int(cpu_time_float))
[235]495    checkpoint.checkpoint('Status')
[133]496    display_fields = """name uptime memory state cpu_weight on_reboot
497     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
498    display_fields = [('name', 'Name'),
499                      ('owner', 'Owner'),
[187]500                      ('administrator', 'Administrator'),
[133]501                      ('contact', 'Contact'),
[136]502                      ('type', 'Type'),
[133]503                      'NIC_INFO',
504                      ('uptime', 'uptime'),
505                      ('cputime', 'CPU usage'),
506                      ('memory', 'RAM'),
507                      'DISK_INFO',
508                      ('state', 'state (xen format)'),
[139]509                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
[133]510                      ('on_reboot', 'Action on VM reboot'),
511                      ('on_poweroff', 'Action on VM poweroff'),
512                      ('on_crash', 'Action on VM crash'),
513                      ('on_xend_start', 'Action on Xen start'),
514                      ('on_xend_stop', 'Action on Xen stop'),
515                      ('bootloader', 'Bootloader options'),
516                      ]
517    fields = []
518    machine_info = {}
[147]519    machine_info['name'] = machine.name
[136]520    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]521    machine_info['owner'] = machine.owner
[187]522    machine_info['administrator'] = machine.administrator
[133]523    machine_info['contact'] = machine.contact
524
525    nic_fields = getNicInfo(machine_info, machine)
526    nic_point = display_fields.index('NIC_INFO')
[205]527    display_fields = (display_fields[:nic_point] + nic_fields + 
528                      display_fields[nic_point+1:])
[133]529
530    disk_fields = getDiskInfo(machine_info, machine)
531    disk_point = display_fields.index('DISK_INFO')
[205]532    display_fields = (display_fields[:disk_point] + disk_fields + 
533                      display_fields[disk_point+1:])
[133]534   
[211]535    main_status['memory'] += ' MiB'
[133]536    for field, disp in display_fields:
[167]537        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]538            fields.append((disp, locals()[field]))
[147]539        elif field in machine_info:
540            fields.append((disp, machine_info[field]))
[133]541        elif field in main_status:
542            fields.append((disp, main_status[field]))
543        else:
544            pass
545            #fields.append((disp, None))
[235]546
547    checkpoint.checkpoint('Got fields')
548
549
550    max_mem = validation.maxMemory(user, machine, False)
551    checkpoint.checkpoint('Got mem')
[209]552    max_disk = validation.maxDisk(user, machine)
553    defaults = Defaults()
[205]554    for name in 'machine_id name administrator owner memory contact'.split():
555        setattr(defaults, name, getattr(machine, name))
556    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
[235]557    checkpoint.checkpoint('Got defaults')
[113]558    d = dict(user=user,
[133]559             cdroms=CDROM.select(),
560             on=status is not None,
561             machine=machine,
[205]562             defaults=defaults,
[133]563             has_vnc=has_vnc,
564             uptime=str(uptime),
565             ram=machine.memory,
[144]566             max_mem=max_mem,
567             max_disk=max_disk,
[166]568             owner_help=helppopup("owner"),
[133]569             fields = fields)
[205]570    return d
[113]571
[205]572def info(user, fields):
573    """Handler for info on a single VM."""
[209]574    machine = validation.testMachineId(user, fields.getfirst('machine_id'))
[205]575    d = infoDict(user, machine)
[235]576    checkpoint.checkpoint('Got infodict')
577    return templates.info(searchList=[d])
[205]578
[113]579mapping = dict(list=listVms,
580               vnc=vnc,
[133]581               command=command,
582               modify=modify,
[113]583               info=info,
[139]584               create=create,
[205]585               help=helpHandler)
[113]586
[205]587def printHeaders(headers):
588    for key, value in headers.iteritems():
589        print '%s: %s' % (key, value)
590    print
591
592
593def getUser():
594    """Return the current user based on the SSL environment variables"""
[254]595    username = os.environ['SSL_CLIENT_S_DN_Email'].split("@")[0]
596    return username
[205]597
[209]598def main(operation, user, fields):   
[235]599    start_time = time.time()
[153]600    fun = mapping.get(operation, badOperation)
[205]601
602    if fun not in (helpHandler, ):
603        connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
[119]604    try:
[235]605        checkpoint.checkpoint('Before')
[153]606        output = fun(u, fields)
[235]607        checkpoint.checkpoint('After')
[205]608
[209]609        headers = dict(DEFAULT_HEADERS)
[205]610        if isinstance(output, tuple):
611            new_headers, output = output
612            headers.update(new_headers)
613        e = revertStandardError()
[153]614        if e:
[205]615            output.addError(e)
616        printHeaders(headers)
[235]617        output_string =  str(output)
618        checkpoint.checkpoint('output as a string')
619        print output_string
[421]620        print '<!-- <pre>%s</pre> -->' % checkpoint
[205]621    except Exception, err:
622        if not fields.has_key('js'):
623            if isinstance(err, CodeError):
624                print 'Content-Type: text/html\n'
625                e = revertStandardError()
626                print error(operation, u, fields, err, e)
627                sys.exit(1)
628            if isinstance(err, InvalidInput):
629                print 'Content-Type: text/html\n'
630                e = revertStandardError()
631                print invalidInput(operation, u, fields, err, e)
632                sys.exit(1)
[153]633        print 'Content-Type: text/plain\n'
[205]634        print 'Uh-oh!  We experienced an error.'
635        print 'Please email sipb-xen@mit.edu with the contents of this page.'
636        print '----'
637        e = revertStandardError()
[153]638        print e
639        print '----'
640        raise
[209]641
642if __name__ == '__main__':
643    fields = cgi.FieldStorage()
644    u = getUser()
645    g.user = u
646    operation = os.environ.get('PATH_INFO', '')
647    if not operation:
648        print "Status: 301 Moved Permanently"
649        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
650        sys.exit(0)
651
652    if operation.startswith('/'):
653        operation = operation[1:]
654    if not operation:
655        operation = 'list'
656
[248]657    if os.getenv("SIPB_XEN_PROFILE"):
658        import profile
659        profile.run('main(operation, u, fields)', 'log-'+operation)
660    else:
661        main(operation, u, fields)
Note: See TracBrowser for help on using the repository browser.