source: trunk/packages/invirt-web/code/main.py @ 2217

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

Use the newly globalized adminacl instead of the old web.adminacl.

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