bugfix
[invirt/packages/invirt-web.git] / templates / main.py
1 #!/usr/bin/python
2
3 import sys
4 import cgi
5 import os
6 import string
7 import subprocess
8 import re
9 import time
10 import cPickle
11 import base64
12 import sha
13 import hmac
14 import datetime
15 import StringIO
16
17 sys.stderr = StringIO.StringIO()
18 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
19
20 from Cheetah.Template import Template
21 from sipb_xen_database import *
22 import random
23
24 class MyException(Exception):
25     """Base class for my exceptions"""
26     pass
27
28 class InvalidInput(MyException):
29     """Exception for user-provided input is invalid but maybe in good faith.
30
31     This would include setting memory to negative (which might be a
32     typo) but not setting an invalid boot CD (which requires bypassing
33     the select box).
34     """
35     def __init__(self, err_field, err_value, expl=None):
36         super(InvalidInput, self).__init__(expl)
37         self.err_field = err_field
38         self.err_value = err_value
39
40 class CodeError(MyException):
41     """Exception for internal errors or bad faith input."""
42     pass
43
44 class Global(object):
45     def __init__(self, user):
46         self.user = user
47
48     def __get_uptimes(self):
49         if not hasattr(self, '_uptimes'):
50             self._uptimes = getUptimes(self.machines)
51         return self._uptimes
52     uptimes = property(__get_uptimes)
53
54 g = None
55
56 def helppopup(subj):
57     """Return HTML code for a (?) link to a specified help topic"""
58     return '<span class="helplink"><a href="help?subject='+subj+'&amp;simple=true" target="_blank" onclick="return helppopup(\''+subj+'\')">(?)</a></span>'
59
60
61 global_dict = {}
62 global_dict['helppopup'] = helppopup
63
64
65 # ... and stolen from xend/uuid.py
66 def randomUUID():
67     """Generate a random UUID."""
68
69     return [ random.randint(0, 255) for _ in range(0, 16) ]
70
71 def uuidToString(u):
72     """Turn a numeric UUID to a hyphen-seperated one."""
73     return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
74                      "%02x" * 6]) % tuple(u)
75
76 MAX_MEMORY_TOTAL = 512
77 MAX_MEMORY_SINGLE = 256
78 MIN_MEMORY_SINGLE = 16
79 MAX_DISK_TOTAL = 50
80 MAX_DISK_SINGLE = 50
81 MIN_DISK_SINGLE = 0.1
82 MAX_VMS_TOTAL = 10
83 MAX_VMS_ACTIVE = 4
84
85 def getMachinesByOwner(owner):
86     """Return the machines owned by a given owner."""
87     return Machine.select_by(owner=owner)
88
89 def maxMemory(user, machine=None):
90     """Return the maximum memory for a machine or a user.
91
92     If machine is None, return the memory available for a new 
93     machine.  Else, return the maximum that machine can have.
94
95     on is a dictionary from machines to booleans, whether a machine is
96     on.  If None, it is recomputed. XXX make this global?
97     """
98
99     machines = getMachinesByOwner(user.username)
100     active_machines = [x for x in machines if g.uptimes[x]]
101     mem_usage = sum([x.memory for x in active_machines if x != machine])
102     return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
103
104 def maxDisk(user, machine=None):
105     machines = getMachinesByOwner(user.username)
106     disk_usage = sum([sum([y.size for y in x.disks])
107                       for x in machines if x != machine])
108     return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
109
110 def canAddVm(user):
111     machines = getMachinesByOwner(user.username)
112     active_machines = [x for x in machines if g.uptimes[x]]
113     return (len(machines) < MAX_VMS_TOTAL and
114             len(active_machines) < MAX_VMS_ACTIVE)
115
116 def haveAccess(user, machine):
117     """Return whether a user has access to a machine"""
118     if user.username == 'moo':
119         return True
120     return machine.owner == user.username
121
122 def error(op, user, fields, err, emsg):
123     """Print an error page when a CodeError occurs"""
124     d = dict(op=op, user=user, errorMessage=str(err),
125              stderr=emsg)
126     return Template(file='error.tmpl', searchList=[d, global_dict]);
127
128 def invalidInput(op, user, fields, err, emsg):
129     """Print an error page when an InvalidInput exception occurs"""
130     d = dict(op=op, user=user, err_field=err.err_field,
131              err_value=str(err.err_value), stderr=emsg,
132              errorMessage=str(err))
133     return Template(file='invalid.tmpl', searchList=[d, global_dict]);
134
135 def validMachineName(name):
136     """Check that name is valid for a machine name"""
137     if not name:
138         return False
139     charset = string.ascii_letters + string.digits + '-_'
140     if name[0] in '-_' or len(name) > 22:
141         return False
142     for x in name:
143         if x not in charset:
144             return False
145     return True
146
147 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
148     """Kinit with a given username and keytab"""
149
150     p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
151                          stderr=subprocess.PIPE)
152     e = p.wait()
153     if e:
154         raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
155
156 def checkKinit():
157     """If we lack tickets, kinit."""
158     p = subprocess.Popen(['klist', '-s'])
159     if p.wait():
160         kinit()
161
162 def remctl(*args, **kws):
163     """Perform a remctl and return the output.
164
165     kinits if necessary, and outputs errors to stderr.
166     """
167     checkKinit()
168     p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
169                          + list(args),
170                          stdout=subprocess.PIPE,
171                          stderr=subprocess.PIPE)
172     if kws.get('err'):
173         p.wait()
174         return p.stdout.read(), p.stderr.read()
175     if p.wait():
176         raise CodeError('ERROR on remctl %s: %s' %
177                           (args, p.stderr.read()))
178     return p.stdout.read()
179
180 def lvcreate(machine, disk):
181     """Create a single disk for a machine"""
182     remctl('web', 'lvcreate', machine.name,
183            disk.guest_device_name, str(disk.size))
184     
185 def makeDisks(machine):
186     """Update the lvm partitions to add a disk."""
187     for disk in machine.disks:
188         lvcreate(machine, disk)
189
190 def bootMachine(machine, cdtype):
191     """Boot a machine with a given boot CD.
192
193     If cdtype is None, give no boot cd.  Otherwise, it is the string
194     id of the CD (e.g. 'gutsy_i386')
195     """
196     if cdtype is not None:
197         remctl('web', 'vmboot', machine.name,
198                cdtype)
199     else:
200         remctl('web', 'vmboot', machine.name)
201
202 def registerMachine(machine):
203     """Register a machine to be controlled by the web interface"""
204     remctl('web', 'register', machine.name)
205
206 def unregisterMachine(machine):
207     """Unregister a machine to not be controlled by the web interface"""
208     remctl('web', 'unregister', machine.name)
209
210 def parseStatus(s):
211     """Parse a status string into nested tuples of strings.
212
213     s = output of xm list --long <machine_name>
214     """
215     values = re.split('([()])', s)
216     stack = [[]]
217     for v in values[2:-2]: #remove initial and final '()'
218         if not v:
219             continue
220         v = v.strip()
221         if v == '(':
222             stack.append([])
223         elif v == ')':
224             if len(stack[-1]) == 1:
225                 stack[-1].append('')
226             stack[-2].append(stack[-1])
227             stack.pop()
228         else:
229             if not v:
230                 continue
231             stack[-1].extend(v.split())
232     return stack[-1]
233
234 def getUptimes(machines):
235     """Return a dictionary mapping machine names to uptime strings"""
236     value_string = remctl('web', 'listvms')
237     lines = value_string.splitlines()
238     d = {}
239     for line in lines:
240         lst = line.split()
241         name, id = lst[:2]
242         uptime = ' '.join(lst[2:])
243         d[name] = uptime
244     ans = {}
245     for m in machines:
246         ans[m] = d.get(m.name)
247     return ans
248
249 def statusInfo(machine):
250     """Return the status list for a given machine.
251
252     Gets and parses xm list --long
253     """
254     value_string, err_string = remctl('list-long', machine.name, err=True)
255     if 'Unknown command' in err_string:
256         raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
257     elif 'does not exist' in err_string:
258         return None
259     elif err_string:
260         raise CodeError("ERROR in remctl list-long %s:  %s" % (machine.name, err_string))
261     status = parseStatus(value_string)
262     return status
263
264 def hasVnc(status):
265     """Does the machine with a given status list support VNC?"""
266     if status is None:
267         return False
268     for l in status:
269         if l[0] == 'device' and l[1][0] == 'vfb':
270             d = dict(l[1][1:])
271             return 'location' in d
272     return False
273
274 def createVm(user, name, memory, disk, is_hvm, cdrom):
275     """Create a VM and put it in the database"""
276     # put stuff in the table
277     transaction = ctx.current.create_transaction()
278     try:
279         if memory > maxMemory(user):
280             raise InvalidInput('memory', memory,
281                                "Max %s" % maxMemory(user))
282         if disk > maxDisk(user) * 1024:
283             raise InvalidInput('disk', disk,
284                                "Max %s" % maxDisk(user))
285         if not canAddVm(user):
286             raise InvalidInput('create', True, 'Unable to create more VMs')
287         res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
288         id = res.fetchone()[0]
289         machine = Machine()
290         machine.machine_id = id
291         machine.name = name
292         machine.memory = memory
293         machine.owner = user.username
294         machine.contact = user.email
295         machine.uuid = uuidToString(randomUUID())
296         machine.boot_off_cd = True
297         machine_type = Type.get_by(hvm=is_hvm)
298         machine.type_id = machine_type.type_id
299         ctx.current.save(machine)
300         disk = Disk(machine.machine_id, 
301                     'hda', disk)
302         open = NIC.select_by(machine_id=None)
303         if not open: #No IPs left!
304             raise CodeError("No IP addresses left!  Contact sipb-xen-dev@mit.edu")
305         nic = open[0]
306         nic.machine_id = machine.machine_id
307         nic.hostname = name
308         ctx.current.save(nic)    
309         ctx.current.save(disk)
310         transaction.commit()
311     except:
312         transaction.rollback()
313         raise
314     registerMachine(machine)
315     makeDisks(machine)
316     # tell it to boot with cdrom
317     bootMachine(machine, cdrom)
318
319     return machine
320
321 def validMemory(user, memory, machine=None):
322     """Parse and validate limits for memory for a given user and machine."""
323     try:
324         memory = int(memory)
325         if memory < MIN_MEMORY_SINGLE:
326             raise ValueError
327     except ValueError:
328         raise InvalidInput('memory', memory, 
329                            "Minimum %s MB" % MIN_MEMORY_SINGLE)
330     if memory > maxMemory(user, machine):
331         raise InvalidInput('memory', memory,
332                            'Maximum %s MB' % maxMemory(user, machine))
333     return memory
334
335 def validDisk(user, disk, machine=None):
336     """Parse and validate limits for disk for a given user and machine."""
337     try:
338         disk = float(disk)
339         if disk > maxDisk(user, machine):
340             raise InvalidInput('disk', disk,
341                                "Maximum %s G" % maxDisk(user, machine))
342         disk = int(disk * 1024)
343         if disk < MIN_DISK_SINGLE * 1024:
344             raise ValueError
345     except ValueError:
346         raise InvalidInput('disk', disk,
347                            "Minimum %s GB" % MIN_DISK_SINGLE)
348     return disk
349
350 def create(user, fields):
351     """Handler for create requests."""
352     name = fields.getfirst('name')
353     if not validMachineName(name):
354         raise InvalidInput('name', name)
355     name = user.username + '_' + name.lower()
356
357     if Machine.get_by(name=name):
358         raise InvalidInput('name', name,
359                            "Already exists")
360     
361     memory = fields.getfirst('memory')
362     memory = validMemory(user, memory)
363     
364     disk = fields.getfirst('disk')
365     disk = validDisk(user, disk)
366
367     vm_type = fields.getfirst('vmtype')
368     if vm_type not in ('hvm', 'paravm'):
369         raise CodeError("Invalid vm type '%s'"  % vm_type)    
370     is_hvm = (vm_type == 'hvm')
371
372     cdrom = fields.getfirst('cdrom')
373     if cdrom is not None and not CDROM.get(cdrom):
374         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
375     
376     machine = createVm(user, name, memory, disk, is_hvm, cdrom)
377     d = dict(user=user,
378              machine=machine)
379     return Template(file='create.tmpl',
380                    searchList=[d, global_dict]);
381
382 def listVms(user, fields):
383     """Handler for list requests."""
384     machines = [m for m in Machine.select() if haveAccess(user, m)]    
385     on = {}
386     has_vnc = {}
387     on = g.uptimes
388     for m in machines:
389         if not on[m]:
390             has_vnc[m] = 'Off'
391         elif m.type.hvm:
392             has_vnc[m] = True
393         else:
394             has_vnc[m] = "ParaVM"+helppopup("paravm_console")
395     #     for m in machines:
396     #         status = statusInfo(m)
397     #         on[m.name] = status is not None
398     #         has_vnc[m.name] = hasVnc(status)
399     max_mem=maxMemory(user)
400     max_disk=maxDisk(user)
401     d = dict(user=user,
402              can_add_vm=canAddVm(user),
403              max_mem=max_mem,
404              max_disk=max_disk,
405              default_mem=max_mem,
406              default_disk=min(4.0, max_disk),
407              machines=machines,
408              has_vnc=has_vnc,
409              uptimes=uptimes,
410              cdroms=CDROM.select())
411     return Template(file='list.tmpl', searchList=[d, global_dict])
412
413 def testMachineId(user, machineId, exists=True):
414     """Parse, validate and check authorization for a given machineId.
415
416     If exists is False, don't check that it exists.
417     """
418     if machineId is None:
419         raise CodeError("No machine ID specified")
420     try:
421         machineId = int(machineId)
422     except ValueError:
423         raise CodeError("Invalid machine ID '%s'" % machineId)
424     machine = Machine.get(machineId)
425     if exists and machine is None:
426         raise CodeError("No such machine ID '%s'" % machineId)
427     if machine is not None and not haveAccess(user, machine):
428         raise CodeError("No access to machine ID '%s'" % machineId)
429     return machine
430
431 def vnc(user, fields):
432     """VNC applet page.
433
434     Note that due to same-domain restrictions, the applet connects to
435     the webserver, which needs to forward those requests to the xen
436     server.  The Xen server runs another proxy that (1) authenticates
437     and (2) finds the correct port for the VM.
438
439     You might want iptables like:
440
441     -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 18.181.0.60:10003 
442     -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp --dport 10003 -j SNAT --to-source 18.187.7.142 
443     -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
444
445     Remember to enable iptables!
446     echo 1 > /proc/sys/net/ipv4/ip_forward
447     """
448     machine = testMachineId(user, fields.getfirst('machine_id'))
449     
450     TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
451
452     data = {}
453     data["user"] = user.username
454     data["machine"]=machine.name
455     data["expires"]=time.time()+(5*60)
456     pickledData = cPickle.dumps(data)
457     m = hmac.new(TOKEN_KEY, digestmod=sha)
458     m.update(pickledData)
459     token = {'data': pickledData, 'digest': m.digest()}
460     token = cPickle.dumps(token)
461     token = base64.urlsafe_b64encode(token)
462     
463     status = statusInfo(machine)
464     has_vnc = hasVnc(status)
465     
466     d = dict(user=user,
467              on=status,
468              has_vnc=has_vnc,
469              machine=machine,
470              hostname=os.environ.get('SERVER_NAME', 'localhost'),
471              authtoken=token)
472     return Template(file='vnc.tmpl',
473                    searchList=[d, global_dict])
474
475 def getNicInfo(data_dict, machine):
476     """Helper function for info, get data on nics for a machine.
477
478     Modifies data_dict to include the relevant data, and returns a list
479     of (key, name) pairs to display "name: data_dict[key]" to the user.
480     """
481     data_dict['num_nics'] = len(machine.nics)
482     nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
483                            ('nic%s_mac', 'NIC %s MAC Addr'),
484                            ('nic%s_ip', 'NIC %s IP'),
485                            ]
486     nic_fields = []
487     for i in range(len(machine.nics)):
488         nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
489         data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
490         data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
491         data_dict['nic%s_ip' % i] = machine.nics[i].ip
492     if len(machine.nics) == 1:
493         nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
494     return nic_fields
495
496 def getDiskInfo(data_dict, machine):
497     """Helper function for info, get data on disks for a machine.
498
499     Modifies data_dict to include the relevant data, and returns a list
500     of (key, name) pairs to display "name: data_dict[key]" to the user.
501     """
502     data_dict['num_disks'] = len(machine.disks)
503     disk_fields_template = [('%s_size', '%s size')]
504     disk_fields = []
505     for disk in machine.disks:
506         name = disk.guest_device_name
507         disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
508         data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
509     return disk_fields
510
511 def deleteVM(machine):
512     """Delete a VM."""
513     transaction = ctx.current.create_transaction()
514     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
515     try:
516         for nic in machine.nics:
517             nic.machine_id = None
518             nic.hostname = None
519             ctx.current.save(nic)
520         for disk in machine.disks:
521             ctx.current.delete(disk)
522         ctx.current.delete(machine)
523         transaction.commit()
524     except:
525         transaction.rollback()
526         raise
527     for mname, dname in delete_disk_pairs:
528         remctl('web', 'lvremove', mname, dname)
529     unregisterMachine(machine)
530
531 def command(user, fields):
532     """Handler for running commands like boot and delete on a VM."""
533     print time.time()-start_time
534     machine = testMachineId(user, fields.getfirst('machine_id'))
535     action = fields.getfirst('action')
536     cdrom = fields.getfirst('cdrom')
537     print time.time()-start_time
538     if cdrom is not None and not CDROM.get(cdrom):
539         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
540     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
541         raise CodeError("Invalid action '%s'" % action)
542     if action == 'Reboot':
543         if cdrom is not None:
544             remctl('reboot', machine.name, cdrom)
545         else:
546             remctl('reboot', machine.name)
547     elif action == 'Power on':
548         if maxMemory(user) < machine.memory:
549             raise InvalidInput('action', 'Power on',
550                                "You don't have enough free RAM quota to turn on this machine")
551         bootMachine(machine, cdrom)
552     elif action == 'Power off':
553         remctl('destroy', machine.name)
554     elif action == 'Shutdown':
555         remctl('shutdown', machine.name)
556     elif action == 'Delete VM':
557         deleteVM(machine)
558     print time.time()-start_time
559
560     d = dict(user=user,
561              command=action,
562              machine=machine)
563     return Template(file="command.tmpl", searchList=[d, global_dict])
564
565 def testOwner(user, owner, machine=None):
566     if owner != user.username:
567         raise InvalidInput('owner', owner,
568                            "Invalid")
569     return owner
570
571 def testContact(user, contact, machine=None):
572     if contact != user.email:
573         raise InvalidInput('contact', contact,
574                            "Invalid")
575     return contact
576
577 def testHostname(user, hostname, machine):
578     for nic in machine.nics:
579         if hostname == nic.hostname:
580             return hostname
581     raise InvalidInput('hostname', hostname,
582                        "Different from before")
583
584
585 def modify(user, fields):
586     """Handler for modifying attributes of a machine."""
587     #XXX not written yet
588     machine = testMachineId(user, fields.getfirst('machine_id'))
589     owner = testOwner(user, fields.getfirst('owner'), machine)
590     contact = testContact(user, fields.getfirst('contact'))
591     hostname = testHostname(user, fields.getfirst('hostname'),
592                             machine)
593     ram = fields.getfirst('memory')
594     if ram is not None:
595         ram = validMemory(user, ram, machine)
596     disk = testDisk(user, fields.getfirst('disk'))
597     if disk is not None:
598         disk = validDisk(user, disk, machine)
599
600     
601
602 def help(user, fields):
603     """Handler for help messages."""
604     simple = fields.getfirst('simple')
605     subjects = fields.getlist('subject')
606     
607     mapping = dict(paravm_console="""
608 ParaVM machines do not support console access over VNC.  To access
609 these machines, you either need to boot with a liveCD and ssh in or
610 hope that the sipb-xen maintainers add support for serial consoles.""",
611                    hvm_paravm="""
612 HVM machines use the virtualization features of the processor, while
613 ParaVM machines use Xen's emulation of virtualization features.  You
614 want an HVM virtualized machine.""",
615                    cpu_weight="""Don't ask us!  We're as mystified as you are.""")
616     
617     d = dict(user=user,
618              simple=simple,
619              subjects=subjects,
620              mapping=mapping)
621     
622     return Template(file="help.tmpl", searchList=[d, global_dict])
623     
624
625 def info(user, fields):
626     """Handler for info on a single VM."""
627     machine = testMachineId(user, fields.getfirst('machine_id'))
628     status = statusInfo(machine)
629     has_vnc = hasVnc(status)
630     if status is None:
631         main_status = dict(name=machine.name,
632                            memory=str(machine.memory))
633     else:
634         main_status = dict(status[1:])
635     start_time = float(main_status.get('start_time', 0))
636     uptime = datetime.timedelta(seconds=int(time.time()-start_time))
637     cpu_time_float = float(main_status.get('cpu_time', 0))
638     cputime = datetime.timedelta(seconds=int(cpu_time_float))
639     display_fields = """name uptime memory state cpu_weight on_reboot 
640      on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
641     display_fields = [('name', 'Name'),
642                       ('owner', 'Owner'),
643                       ('contact', 'Contact'),
644                       ('type', 'Type'),
645                       'NIC_INFO',
646                       ('uptime', 'uptime'),
647                       ('cputime', 'CPU usage'),
648                       ('memory', 'RAM'),
649                       'DISK_INFO',
650                       ('state', 'state (xen format)'),
651                       ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
652                       ('on_reboot', 'Action on VM reboot'),
653                       ('on_poweroff', 'Action on VM poweroff'),
654                       ('on_crash', 'Action on VM crash'),
655                       ('on_xend_start', 'Action on Xen start'),
656                       ('on_xend_stop', 'Action on Xen stop'),
657                       ('bootloader', 'Bootloader options'),
658                       ]
659     fields = []
660     machine_info = {}
661     machine_info['name'] = machine.name
662     machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
663     machine_info['owner'] = machine.owner
664     machine_info['contact'] = machine.contact
665
666     nic_fields = getNicInfo(machine_info, machine)
667     nic_point = display_fields.index('NIC_INFO')
668     display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
669
670     disk_fields = getDiskInfo(machine_info, machine)
671     disk_point = display_fields.index('DISK_INFO')
672     display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
673     
674     main_status['memory'] += ' MB'
675     for field, disp in display_fields:
676         if field in ('uptime', 'cputime'):
677             fields.append((disp, locals()[field]))
678         elif field in machine_info:
679             fields.append((disp, machine_info[field]))
680         elif field in main_status:
681             fields.append((disp, main_status[field]))
682         else:
683             pass
684             #fields.append((disp, None))
685     max_mem = maxMemory(user, machine)
686     max_disk = maxDisk(user, machine)
687     d = dict(user=user,
688              cdroms=CDROM.select(),
689              on=status is not None,
690              machine=machine,
691              has_vnc=has_vnc,
692              uptime=str(uptime),
693              ram=machine.memory,
694              max_mem=max_mem,
695              max_disk=max_disk,
696              fields = fields)
697     return Template(file='info.tmpl',
698                    searchList=[d, global_dict])
699
700 mapping = dict(list=listVms,
701                vnc=vnc,
702                command=command,
703                modify=modify,
704                info=info,
705                create=create,
706                help=help)
707
708 if __name__ == '__main__':
709     start_time = time.time()
710     fields = cgi.FieldStorage()
711     class User:
712         username = "moo"
713         email = 'moo@cow.com'
714     u = User()
715     g = Global(u)
716     if 'SSL_CLIENT_S_DN_Email' in os.environ:
717         username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
718         u.username = username
719         u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
720     else:
721         u.username = 'moo'
722         u.email = 'nobody'
723     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
724     operation = os.environ.get('PATH_INFO', '')
725     #print 'Content-Type: text/plain\n'
726     #print operation
727     if not operation:
728         print "Status: 301 Moved Permanently"
729         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
730         sys.exit(0)
731
732     if operation.startswith('/'):
733         operation = operation[1:]
734     if not operation:
735         operation = 'list'
736     
737     def badOperation(u, e):
738         raise CodeError("Unknown operation")
739
740     fun = mapping.get(operation, badOperation)
741     if fun not in (help, ):
742         connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
743     try:
744         output = fun(u, fields)
745         print 'Content-Type: text/html\n'
746         sys.stderr.seek(0)
747         e = sys.stderr.read()
748         if e:
749             output = output.replace('<body>', '<body><pre>'+e+'</pre>')
750         print output
751     except CodeError, err:
752         print 'Content-Type: text/html\n'
753         sys.stderr.seek(0)
754         e = sys.stderr.read()
755         print error(operation, u, fields, err, e)
756     except InvalidInput, err:
757         print 'Content-Type: text/html\n'
758         sys.stderr.seek(0)
759         e = sys.stderr.read()
760         print invalidInput(operation, u, fields, err, e)
761     except:
762         print 'Content-Type: text/plain\n'
763         sys.stderr.seek(0)
764         e = sys.stderr.read()
765         print e
766         print '----'
767         raise