destroy machines before trying to delete them.
[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(Machine.select())
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=None):
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=g.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     remctl('destroy', machine.name)
514     transaction = ctx.current.create_transaction()
515     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
516     try:
517         for nic in machine.nics:
518             nic.machine_id = None
519             nic.hostname = None
520             ctx.current.save(nic)
521         for disk in machine.disks:
522             ctx.current.delete(disk)
523         ctx.current.delete(machine)
524         transaction.commit()
525     except:
526         transaction.rollback()
527         raise
528     for mname, dname in delete_disk_pairs:
529         remctl('web', 'lvremove', mname, dname)
530     unregisterMachine(machine)
531
532 def command(user, fields):
533     """Handler for running commands like boot and delete on a VM."""
534     print >> sys.stderr, time.time()-start_time
535     machine = testMachineId(user, fields.getfirst('machine_id'))
536     action = fields.getfirst('action')
537     cdrom = fields.getfirst('cdrom')
538     print >> sys.stderr, time.time()-start_time
539     if cdrom is not None and not CDROM.get(cdrom):
540         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
541     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
542         raise CodeError("Invalid action '%s'" % action)
543     if action == 'Reboot':
544         if cdrom is not None:
545             remctl('reboot', machine.name, cdrom)
546         else:
547             remctl('reboot', machine.name)
548     elif action == 'Power on':
549         if maxMemory(user) < machine.memory:
550             raise InvalidInput('action', 'Power on',
551                                "You don't have enough free RAM quota to turn on this machine")
552         bootMachine(machine, cdrom)
553     elif action == 'Power off':
554         remctl('destroy', machine.name)
555     elif action == 'Shutdown':
556         remctl('shutdown', machine.name)
557     elif action == 'Delete VM':
558         deleteVM(machine)
559     print >> sys.stderr, time.time()-start_time
560
561     d = dict(user=user,
562              command=action,
563              machine=machine)
564     return Template(file="command.tmpl", searchList=[d, global_dict])
565
566 def testOwner(user, owner, machine=None):
567     if owner != user.username:
568         raise InvalidInput('owner', owner,
569                            "Invalid")
570     return owner
571
572 def testContact(user, contact, machine=None):
573     if contact != user.email:
574         raise InvalidInput('contact', contact,
575                            "Invalid")
576     return contact
577
578 def testHostname(user, hostname, machine):
579     for nic in machine.nics:
580         if hostname == nic.hostname:
581             return hostname
582     raise InvalidInput('hostname', hostname,
583                        "Different from before")
584
585
586 def modify(user, fields):
587     """Handler for modifying attributes of a machine."""
588     #XXX not written yet
589     machine = testMachineId(user, fields.getfirst('machine_id'))
590     owner = testOwner(user, fields.getfirst('owner'), machine)
591     contact = testContact(user, fields.getfirst('contact'))
592     hostname = testHostname(user, fields.getfirst('hostname'),
593                             machine)
594     ram = fields.getfirst('memory')
595     if ram is not None:
596         ram = validMemory(user, ram, machine)
597     disk = testDisk(user, fields.getfirst('disk'))
598     if disk is not None:
599         disk = validDisk(user, disk, machine)
600
601     
602
603 def help(user, fields):
604     """Handler for help messages."""
605     simple = fields.getfirst('simple')
606     subjects = fields.getlist('subject')
607     
608     mapping = dict(paravm_console="""
609 ParaVM machines do not support console access over VNC.  To access
610 these machines, you either need to boot with a liveCD and ssh in or
611 hope that the sipb-xen maintainers add support for serial consoles.""",
612                    hvm_paravm="""
613 HVM machines use the virtualization features of the processor, while
614 ParaVM machines use Xen's emulation of virtualization features.  You
615 want an HVM virtualized machine.""",
616                    cpu_weight="""Don't ask us!  We're as mystified as you are.""")
617     
618     d = dict(user=user,
619              simple=simple,
620              subjects=subjects,
621              mapping=mapping)
622     
623     return Template(file="help.tmpl", searchList=[d, global_dict])
624     
625
626 def info(user, fields):
627     """Handler for info on a single VM."""
628     machine = testMachineId(user, fields.getfirst('machine_id'))
629     status = statusInfo(machine)
630     has_vnc = hasVnc(status)
631     if status is None:
632         main_status = dict(name=machine.name,
633                            memory=str(machine.memory))
634     else:
635         main_status = dict(status[1:])
636     start_time = float(main_status.get('start_time', 0))
637     uptime = datetime.timedelta(seconds=int(time.time()-start_time))
638     cpu_time_float = float(main_status.get('cpu_time', 0))
639     cputime = datetime.timedelta(seconds=int(cpu_time_float))
640     display_fields = """name uptime memory state cpu_weight on_reboot 
641      on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
642     display_fields = [('name', 'Name'),
643                       ('owner', 'Owner'),
644                       ('contact', 'Contact'),
645                       ('type', 'Type'),
646                       'NIC_INFO',
647                       ('uptime', 'uptime'),
648                       ('cputime', 'CPU usage'),
649                       ('memory', 'RAM'),
650                       'DISK_INFO',
651                       ('state', 'state (xen format)'),
652                       ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
653                       ('on_reboot', 'Action on VM reboot'),
654                       ('on_poweroff', 'Action on VM poweroff'),
655                       ('on_crash', 'Action on VM crash'),
656                       ('on_xend_start', 'Action on Xen start'),
657                       ('on_xend_stop', 'Action on Xen stop'),
658                       ('bootloader', 'Bootloader options'),
659                       ]
660     fields = []
661     machine_info = {}
662     machine_info['name'] = machine.name
663     machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
664     machine_info['owner'] = machine.owner
665     machine_info['contact'] = machine.contact
666
667     nic_fields = getNicInfo(machine_info, machine)
668     nic_point = display_fields.index('NIC_INFO')
669     display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
670
671     disk_fields = getDiskInfo(machine_info, machine)
672     disk_point = display_fields.index('DISK_INFO')
673     display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
674     
675     main_status['memory'] += ' MB'
676     for field, disp in display_fields:
677         if field in ('uptime', 'cputime'):
678             fields.append((disp, locals()[field]))
679         elif field in machine_info:
680             fields.append((disp, machine_info[field]))
681         elif field in main_status:
682             fields.append((disp, main_status[field]))
683         else:
684             pass
685             #fields.append((disp, None))
686     max_mem = maxMemory(user, machine)
687     max_disk = maxDisk(user, machine)
688     d = dict(user=user,
689              cdroms=CDROM.select(),
690              on=status is not None,
691              machine=machine,
692              has_vnc=has_vnc,
693              uptime=str(uptime),
694              ram=machine.memory,
695              max_mem=max_mem,
696              max_disk=max_disk,
697              fields = fields)
698     return Template(file='info.tmpl',
699                    searchList=[d, global_dict])
700
701 mapping = dict(list=listVms,
702                vnc=vnc,
703                command=command,
704                modify=modify,
705                info=info,
706                create=create,
707                help=help)
708
709 if __name__ == '__main__':
710     start_time = time.time()
711     fields = cgi.FieldStorage()
712     class User:
713         username = "moo"
714         email = 'moo@cow.com'
715     u = User()
716     g = Global(u)
717     if 'SSL_CLIENT_S_DN_Email' in os.environ:
718         username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
719         u.username = username
720         u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
721     else:
722         u.username = 'moo'
723         u.email = 'nobody'
724     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
725     operation = os.environ.get('PATH_INFO', '')
726 #    print 'Content-Type: text/plain\n'
727 #    print operation
728     if not operation:
729         print "Status: 301 Moved Permanently"
730         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
731         sys.exit(0)
732
733     if operation.startswith('/'):
734         operation = operation[1:]
735     if not operation:
736         operation = 'list'
737
738     def badOperation(u, e):
739         raise CodeError("Unknown operation")
740
741     fun = mapping.get(operation, badOperation)
742     if fun not in (help, ):
743         connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
744     try:
745         output = fun(u, fields)
746         print 'Content-Type: text/html\n'
747         sys.stderr.seek(0)
748         e = sys.stderr.read()
749         if e:
750             output = str(output)
751             output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
752         print output
753     except CodeError, err:
754         print 'Content-Type: text/html\n'
755         sys.stderr.seek(0)
756         e = sys.stderr.read()
757         sys.stderr=sys.stdout
758         print error(operation, u, fields, err, e)
759     except InvalidInput, err:
760         print 'Content-Type: text/html\n'
761         sys.stderr.seek(0)
762         e = sys.stderr.read()
763         sys.stderr=sys.stdout
764         print invalidInput(operation, u, fields, err, e)
765     except:
766         print 'Content-Type: text/plain\n'
767         sys.stderr.seek(0)
768         e = sys.stderr.read()
769         print e
770         print '----'
771         sys.stderr = sys.stdout
772         raise