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

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

Connect to the database on init

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