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