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

Last change on this file since 2663 was 2663, checked in by broder, 15 years ago

Stub InvirtWeb? implementation

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