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

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

finish sipb_xen_database -> invirt.database in web

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