18 errio = StringIO.StringIO()
20 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
22 from Cheetah.Template import Template
23 from sipb_xen_database import *
26 class MyException(Exception):
27 """Base class for my exceptions"""
30 class InvalidInput(MyException):
31 """Exception for user-provided input is invalid but maybe in good faith.
33 This would include setting memory to negative (which might be a
34 typo) but not setting an invalid boot CD (which requires bypassing
37 def __init__(self, err_field, err_value, expl=None):
38 MyException.__init__(self, expl)
39 self.err_field = err_field
40 self.err_value = err_value
42 class CodeError(MyException):
43 """Exception for internal errors or bad faith input."""
47 def __init__(self, user):
50 def __get_uptimes(self):
51 if not hasattr(self, '_uptimes'):
52 self._uptimes = getUptimes(Machine.select())
54 uptimes = property(__get_uptimes)
59 """Return HTML code for a (?) link to a specified help topic"""
60 return '<span class="helplink"><a href="help?subject='+subj+'&simple=true" target="_blank" onclick="return helppopup(\''+subj+'\')">(?)</a></span>'
64 global_dict['helppopup'] = helppopup
67 # ... and stolen from xend/uuid.py
69 """Generate a random UUID."""
71 return [ random.randint(0, 255) for _ in range(0, 16) ]
74 """Turn a numeric UUID to a hyphen-seperated one."""
75 return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
76 "%02x" * 6]) % tuple(u)
78 MAX_MEMORY_TOTAL = 512
79 MAX_MEMORY_SINGLE = 256
80 MIN_MEMORY_SINGLE = 16
87 def getMachinesByOwner(user, machine=None):
88 """Return the machines owned by the same as a machine.
90 If the machine is None, return the machines owned by the same
97 return Machine.select_by(owner=owner)
99 def maxMemory(user, machine=None, on=True):
100 """Return the maximum memory for a machine or a user.
102 If machine is None, return the memory available for a new
103 machine. Else, return the maximum that machine can have.
105 on is whether the machine should be turned on. If false, the max
106 memory for the machine to change to, if it is left off, is
110 return MAX_MEMORY_SINGLE
111 machines = getMachinesByOwner(user, machine)
112 active_machines = [x for x in machines if g.uptimes[x]]
113 mem_usage = sum([x.memory for x in active_machines if x != machine])
114 return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
116 def maxDisk(user, machine=None):
117 machines = getMachinesByOwner(user, machine)
118 disk_usage = sum([sum([y.size for y in x.disks])
119 for x in machines if x != machine])
120 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
123 machines = getMachinesByOwner(user)
124 active_machines = [x for x in machines if g.uptimes[x]]
125 return (len(machines) < MAX_VMS_TOTAL and
126 len(active_machines) < MAX_VMS_ACTIVE)
128 def haveAccess(user, machine):
129 """Return whether a user has adminstrative access to a machine"""
130 if user.username == 'moo':
132 if user.username in (machine.administrator, machine.owner):
134 if getafsgroups.checkAfsGroup(user.username, machine.administrator, 'athena.mit.edu'): #XXX Cell?
136 if getafsgroups.checkLockerOwner(user.username, machine.owner):
138 return owns(user, machine)
140 def owns(user, machine):
141 """Return whether a user owns a machine"""
142 if user.username == 'moo':
144 return getafsgroups.checkLockerOwner(user.username, machine.owner)
146 def error(op, user, fields, err, emsg):
147 """Print an error page when a CodeError occurs"""
148 d = dict(op=op, user=user, errorMessage=str(err),
150 return Template(file='error.tmpl', searchList=[d, global_dict]);
152 def invalidInput(op, user, fields, err, emsg):
153 """Print an error page when an InvalidInput exception occurs"""
154 d = dict(op=op, user=user, err_field=err.err_field,
155 err_value=str(err.err_value), stderr=emsg,
156 errorMessage=str(err))
157 return Template(file='invalid.tmpl', searchList=[d, global_dict]);
159 def validMachineName(name):
160 """Check that name is valid for a machine name"""
163 charset = string.ascii_letters + string.digits + '-_'
164 if name[0] in '-_' or len(name) > 22:
171 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
172 """Kinit with a given username and keytab"""
174 p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
175 stderr=subprocess.PIPE)
178 raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
181 """If we lack tickets, kinit."""
182 p = subprocess.Popen(['klist', '-s'])
186 def remctl(*args, **kws):
187 """Perform a remctl and return the output.
189 kinits if necessary, and outputs errors to stderr.
192 p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
194 stdout=subprocess.PIPE,
195 stderr=subprocess.PIPE)
198 return p.stdout.read(), p.stderr.read()
200 print >> sys.stderr, 'Error on remctl', args, ':'
201 print >> sys.stderr, p.stderr.read()
202 raise CodeError('ERROR on remctl')
203 return p.stdout.read()
205 def lvcreate(machine, disk):
206 """Create a single disk for a machine"""
207 remctl('web', 'lvcreate', machine.name,
208 disk.guest_device_name, str(disk.size))
210 def makeDisks(machine):
211 """Update the lvm partitions to add a disk."""
212 for disk in machine.disks:
213 lvcreate(machine, disk)
215 def bootMachine(machine, cdtype):
216 """Boot a machine with a given boot CD.
218 If cdtype is None, give no boot cd. Otherwise, it is the string
219 id of the CD (e.g. 'gutsy_i386')
221 if cdtype is not None:
222 remctl('control', machine.name, 'create',
225 remctl('control', machine.name, 'create')
227 def registerMachine(machine):
228 """Register a machine to be controlled by the web interface"""
229 remctl('web', 'register', machine.name)
231 def unregisterMachine(machine):
232 """Unregister a machine to not be controlled by the web interface"""
233 remctl('web', 'unregister', machine.name)
236 """Parse a status string into nested tuples of strings.
238 s = output of xm list --long <machine_name>
240 values = re.split('([()])', s)
242 for v in values[2:-2]: #remove initial and final '()'
249 if len(stack[-1]) == 1:
251 stack[-2].append(stack[-1])
256 stack[-1].extend(v.split())
259 def getUptimes(machines=None):
260 """Return a dictionary mapping machine names to uptime strings"""
261 value_string = remctl('web', 'listvms')
262 lines = value_string.splitlines()
267 uptime = ' '.join(lst[2:])
271 ans[m] = d.get(m.name)
274 def statusInfo(machine):
275 """Return the status list for a given machine.
277 Gets and parses xm list --long
279 value_string, err_string = remctl('control', machine.name, 'list-long', err=True)
280 if 'Unknown command' in err_string:
281 raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
282 elif 'does not exist' in err_string:
285 raise CodeError("ERROR in remctl list-long %s: %s" % (machine.name, err_string))
286 status = parseStatus(value_string)
290 """Does the machine with a given status list support VNC?"""
294 if l[0] == 'device' and l[1][0] == 'vfb':
296 return 'location' in d
299 def createVm(user, name, memory, disk, is_hvm, cdrom):
300 """Create a VM and put it in the database"""
301 # put stuff in the table
302 transaction = ctx.current.create_transaction()
304 if memory > maxMemory(user):
305 raise InvalidInput('memory', memory,
306 "Max %s" % maxMemory(user))
307 if disk > maxDisk(user) * 1024:
308 raise InvalidInput('disk', disk,
309 "Max %s" % maxDisk(user))
310 if not canAddVm(user):
311 raise InvalidInput('create', True, 'Unable to create more VMs')
312 res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
313 id = res.fetchone()[0]
315 machine.machine_id = id
317 machine.memory = memory
318 machine.owner = user.username
319 machine.administrator = user.username
320 machine.contact = user.email
321 machine.uuid = uuidToString(randomUUID())
322 machine.boot_off_cd = True
323 machine_type = Type.get_by(hvm=is_hvm)
324 machine.type_id = machine_type.type_id
325 ctx.current.save(machine)
326 disk = Disk(machine.machine_id,
328 open = NIC.select_by(machine_id=None)
329 if not open: #No IPs left!
330 raise CodeError("No IP addresses left! Contact sipb-xen-dev@mit.edu")
332 nic.machine_id = machine.machine_id
334 ctx.current.save(nic)
335 ctx.current.save(disk)
338 transaction.rollback()
340 registerMachine(machine)
342 # tell it to boot with cdrom
343 bootMachine(machine, cdrom)
347 def validMemory(user, memory, machine=None, on=True):
348 """Parse and validate limits for memory for a given user and machine.
350 on is whether the memory must be valid after the machine is
355 if memory < MIN_MEMORY_SINGLE:
358 raise InvalidInput('memory', memory,
359 "Minimum %s MB" % MIN_MEMORY_SINGLE)
360 if memory > maxMemory(user, machine, on):
361 raise InvalidInput('memory', memory,
362 'Maximum %s MB' % maxMemory(user, machine))
365 def validDisk(user, disk, machine=None):
366 """Parse and validate limits for disk for a given user and machine."""
369 if disk > maxDisk(user, machine):
370 raise InvalidInput('disk', disk,
371 "Maximum %s G" % maxDisk(user, machine))
372 disk = int(disk * 1024)
373 if disk < MIN_DISK_SINGLE * 1024:
376 raise InvalidInput('disk', disk,
377 "Minimum %s GB" % MIN_DISK_SINGLE)
380 def create(user, fields):
381 """Handler for create requests."""
382 name = fields.getfirst('name')
383 if not validMachineName(name):
384 raise InvalidInput('name', name)
387 if Machine.get_by(name=name):
388 raise InvalidInput('name', name,
391 memory = fields.getfirst('memory')
392 memory = validMemory(user, memory, on=True)
394 disk = fields.getfirst('disk')
395 disk = validDisk(user, disk)
397 vm_type = fields.getfirst('vmtype')
398 if vm_type not in ('hvm', 'paravm'):
399 raise CodeError("Invalid vm type '%s'" % vm_type)
400 is_hvm = (vm_type == 'hvm')
402 cdrom = fields.getfirst('cdrom')
403 if cdrom is not None and not CDROM.get(cdrom):
404 raise CodeError("Invalid cdrom type '%s'" % cdrom)
406 machine = createVm(user, name, memory, disk, is_hvm, cdrom)
409 return Template(file='create.tmpl',
410 searchList=[d, global_dict]);
412 def listVms(user, fields):
413 """Handler for list requests."""
414 machines = [m for m in Machine.select() if haveAccess(user, m)]
424 has_vnc[m] = "ParaVM"+helppopup("paravm_console")
426 # status = statusInfo(m)
427 # on[m.name] = status is not None
428 # has_vnc[m.name] = hasVnc(status)
429 max_mem=maxMemory(user)
430 max_disk=maxDisk(user)
432 can_add_vm=canAddVm(user),
436 default_disk=min(4.0, max_disk),
440 cdroms=CDROM.select())
441 return Template(file='list.tmpl', searchList=[d, global_dict])
443 def testMachineId(user, machineId, exists=True):
444 """Parse, validate and check authorization for a given machineId.
446 If exists is False, don't check that it exists.
448 if machineId is None:
449 raise CodeError("No machine ID specified")
451 machineId = int(machineId)
453 raise CodeError("Invalid machine ID '%s'" % machineId)
454 machine = Machine.get(machineId)
455 if exists and machine is None:
456 raise CodeError("No such machine ID '%s'" % machineId)
457 if machine is not None and not haveAccess(user, machine):
458 raise CodeError("No access to machine ID '%s'" % machineId)
461 def vnc(user, fields):
464 Note that due to same-domain restrictions, the applet connects to
465 the webserver, which needs to forward those requests to the xen
466 server. The Xen server runs another proxy that (1) authenticates
467 and (2) finds the correct port for the VM.
469 You might want iptables like:
471 -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
472 -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
473 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
475 Remember to enable iptables!
476 echo 1 > /proc/sys/net/ipv4/ip_forward
478 machine = testMachineId(user, fields.getfirst('machine_id'))
480 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
483 data["user"] = user.username
484 data["machine"]=machine.name
485 data["expires"]=time.time()+(5*60)
486 pickledData = cPickle.dumps(data)
487 m = hmac.new(TOKEN_KEY, digestmod=sha)
488 m.update(pickledData)
489 token = {'data': pickledData, 'digest': m.digest()}
490 token = cPickle.dumps(token)
491 token = base64.urlsafe_b64encode(token)
493 status = statusInfo(machine)
494 has_vnc = hasVnc(status)
500 hostname=os.environ.get('SERVER_NAME', 'localhost'),
502 return Template(file='vnc.tmpl',
503 searchList=[d, global_dict])
505 def getNicInfo(data_dict, machine):
506 """Helper function for info, get data on nics for a machine.
508 Modifies data_dict to include the relevant data, and returns a list
509 of (key, name) pairs to display "name: data_dict[key]" to the user.
511 data_dict['num_nics'] = len(machine.nics)
512 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
513 ('nic%s_mac', 'NIC %s MAC Addr'),
514 ('nic%s_ip', 'NIC %s IP'),
517 for i in range(len(machine.nics)):
518 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
519 data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
520 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
521 data_dict['nic%s_ip' % i] = machine.nics[i].ip
522 if len(machine.nics) == 1:
523 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
526 def getDiskInfo(data_dict, machine):
527 """Helper function for info, get data on disks for a machine.
529 Modifies data_dict to include the relevant data, and returns a list
530 of (key, name) pairs to display "name: data_dict[key]" to the user.
532 data_dict['num_disks'] = len(machine.disks)
533 disk_fields_template = [('%s_size', '%s size')]
535 for disk in machine.disks:
536 name = disk.guest_device_name
537 disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
538 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
541 def deleteVM(machine):
543 remctl('control', machine.name, 'destroy', err=True)
544 transaction = ctx.current.create_transaction()
545 delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
547 for nic in machine.nics:
548 nic.machine_id = None
550 ctx.current.save(nic)
551 for disk in machine.disks:
552 ctx.current.delete(disk)
553 ctx.current.delete(machine)
556 transaction.rollback()
558 for mname, dname in delete_disk_pairs:
559 remctl('web', 'lvremove', mname, dname)
560 unregisterMachine(machine)
562 def command(user, fields):
563 """Handler for running commands like boot and delete on a VM."""
564 print >> sys.stderr, time.time()-start_time
565 machine = testMachineId(user, fields.getfirst('machine_id'))
566 action = fields.getfirst('action')
567 cdrom = fields.getfirst('cdrom')
568 print >> sys.stderr, time.time()-start_time
569 if cdrom is not None and not CDROM.get(cdrom):
570 raise CodeError("Invalid cdrom type '%s'" % cdrom)
571 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
572 raise CodeError("Invalid action '%s'" % action)
573 if action == 'Reboot':
574 if cdrom is not None:
575 remctl('control', machine.name, 'reboot', cdrom)
577 remctl('control', machine.name, 'reboot')
578 elif action == 'Power on':
579 if maxMemory(user) < machine.memory:
580 raise InvalidInput('action', 'Power on',
581 "You don't have enough free RAM quota to turn on this machine")
582 bootMachine(machine, cdrom)
583 elif action == 'Power off':
584 remctl('control', machine.name, 'destroy')
585 elif action == 'Shutdown':
586 remctl('control', machine.name, 'shutdown')
587 elif action == 'Delete VM':
589 print >> sys.stderr, time.time()-start_time
594 return Template(file="command.tmpl", searchList=[d, global_dict])
596 def testAdmin(user, admin, machine):
597 if admin in (None, machine.administrator):
599 if admin == user.username:
601 if getafsgroups.checkAfsGroup(user.username, admin, 'athena.mit.edu'):
603 if getafsgroups.checkAfsGroup(user.username, 'system:'+admin, 'athena.mit.edu'):
604 return 'system:'+admin
605 raise InvalidInput('admin', admin,
606 'You must control the group you move it to')
608 def testOwner(user, owner, machine):
609 if owner in (None, machine.owner):
611 #XXX should you be able to transfer ownership if you don't already own it?
612 #if not owns(user, machine):
613 # raise InvalidInput('owner', owner, "You don't own this machine, so you can't transfer ownership")
614 value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
617 raise InvalidInput('owner', owner, value)
619 def testContact(user, contact, machine=None):
620 if contact in (None, machine.contact):
622 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
623 raise InvalidInput('contact', contact, "Not a valid email")
626 def testDisk(user, disksize, machine=None):
629 def testName(user, name, machine=None):
630 if name in (None, machine.name):
632 if not Machine.select_by(name=name):
634 raise InvalidInput('name', name, "Already taken")
636 def testHostname(user, hostname, machine):
637 for nic in machine.nics:
638 if hostname == nic.hostname:
640 # check if doesn't already exist
641 if NIC.select_by(hostname=hostname):
642 raise InvalidInput('hostname', hostname,
644 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
645 raise InvalidInput('hostname', hostname, "Not a valid hostname; must only use number, letters, and dashes.")
648 def modify(user, fields):
649 """Handler for modifying attributes of a machine."""
652 transaction = ctx.current.create_transaction()
654 machine = testMachineId(user, fields.getfirst('machine_id'))
655 owner = testOwner(user, fields.getfirst('owner'), machine)
656 admin = testAdmin(user, fields.getfirst('administrator'), machine)
657 contact = testContact(user, fields.getfirst('contact'), machine)
658 hostname = testHostname(owner, fields.getfirst('hostname'), machine)
659 name = testName(user, fields.getfirst('name'), machine)
660 oldname = machine.name
663 memory = fields.getfirst('memory')
664 if memory is not None:
665 memory = validMemory(user, memory, machine, on=False)
666 machine.memory = memory
668 disksize = testDisk(user, fields.getfirst('disk'))
669 if disksize is not None:
670 disksize = validDisk(user, disksize, machine)
671 disk = machine.disks[0]
672 if disk.size != disksize:
673 olddisk[disk.guest_device_name] = disksize
675 ctx.current.save(disk)
677 # XXX first NIC gets hostname on change? Interface doesn't support more.
678 for nic in machine.nics[:1]:
679 nic.hostname = hostname
680 ctx.current.save(nic)
682 if owner is not None:
683 machine.owner = owner
686 if admin is not None:
687 machine.administrator = admin
688 if contact is not None:
689 machine.contact = contact
691 ctx.current.save(machine)
694 transaction.rollback()
696 for diskname in olddisk:
697 remctl("web", "lvresize", oldname, diskname, str(olddisk[diskname]))
699 for disk in machine.disks:
700 remctl("web", "lvrename", oldname, disk.guest_device_name, name)
701 remctl("web", "moveregister", oldname, name)
705 return Template(file="command.tmpl", searchList=[d, global_dict])
708 def help(user, fields):
709 """Handler for help messages."""
710 simple = fields.getfirst('simple')
711 subjects = fields.getlist('subject')
713 mapping = dict(paravm_console="""
714 ParaVM machines do not support console access over VNC. To access
715 these machines, you either need to boot with a liveCD and ssh in or
716 hope that the sipb-xen maintainers add support for serial consoles.""",
718 HVM machines use the virtualization features of the processor, while
719 ParaVM machines use Xen's emulation of virtualization features. You
720 want an HVM virtualized machine.""",
721 cpu_weight="""Don't ask us! We're as mystified as you are.""",
722 owner="""The owner field is used to determine <a href="help?subject=quotas">quotas</a>. It must be the name
723 of a locker that you are an AFS administrator of. In particular, you
724 or an AFS group you are a member of must have AFS rlidwka bits on the
725 locker. You can check see who administers the LOCKER locker using the
726 command 'fs la /mit/LOCKER' on Athena.) See also <a href="help?subject=administrator">administrator</a>.""",
727 administrator="""The administrator field determines who can access the console and power on and off the machine. This can be either a user or a moira group.""",
728 quotas="""Quotas are determined on a per-locker basis. Each
729 quota may have a maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4 active machines."""
734 subjects = sorted(mapping.keys())
741 return Template(file="help.tmpl", searchList=[d, global_dict])
744 def info(user, fields):
745 """Handler for info on a single VM."""
746 machine = testMachineId(user, fields.getfirst('machine_id'))
747 status = statusInfo(machine)
748 has_vnc = hasVnc(status)
750 main_status = dict(name=machine.name,
751 memory=str(machine.memory))
755 main_status = dict(status[1:])
756 start_time = float(main_status.get('start_time', 0))
757 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
758 cpu_time_float = float(main_status.get('cpu_time', 0))
759 cputime = datetime.timedelta(seconds=int(cpu_time_float))
760 display_fields = """name uptime memory state cpu_weight on_reboot
761 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
762 display_fields = [('name', 'Name'),
764 ('administrator', 'Administrator'),
765 ('contact', 'Contact'),
768 ('uptime', 'uptime'),
769 ('cputime', 'CPU usage'),
772 ('state', 'state (xen format)'),
773 ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
774 ('on_reboot', 'Action on VM reboot'),
775 ('on_poweroff', 'Action on VM poweroff'),
776 ('on_crash', 'Action on VM crash'),
777 ('on_xend_start', 'Action on Xen start'),
778 ('on_xend_stop', 'Action on Xen stop'),
779 ('bootloader', 'Bootloader options'),
783 machine_info['name'] = machine.name
784 machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
785 machine_info['owner'] = machine.owner
786 machine_info['administrator'] = machine.administrator
787 machine_info['contact'] = machine.contact
789 nic_fields = getNicInfo(machine_info, machine)
790 nic_point = display_fields.index('NIC_INFO')
791 display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
793 disk_fields = getDiskInfo(machine_info, machine)
794 disk_point = display_fields.index('DISK_INFO')
795 display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
797 main_status['memory'] += ' MB'
798 for field, disp in display_fields:
799 if field in ('uptime', 'cputime') and locals()[field] is not None:
800 fields.append((disp, locals()[field]))
801 elif field in machine_info:
802 fields.append((disp, machine_info[field]))
803 elif field in main_status:
804 fields.append((disp, main_status[field]))
807 #fields.append((disp, None))
808 max_mem = maxMemory(user, machine)
809 max_disk = maxDisk(user, machine)
811 cdroms=CDROM.select(),
812 on=status is not None,
819 owner_help=helppopup("owner"),
821 return Template(file='info.tmpl',
822 searchList=[d, global_dict])
824 mapping = dict(list=listVms,
832 if __name__ == '__main__':
833 start_time = time.time()
834 fields = cgi.FieldStorage()
837 email = 'moo@cow.com'
840 if 'SSL_CLIENT_S_DN_Email' in os.environ:
841 username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
842 u.username = username
843 u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
847 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
848 operation = os.environ.get('PATH_INFO', '')
850 print "Status: 301 Moved Permanently"
851 print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
854 if operation.startswith('/'):
855 operation = operation[1:]
859 def badOperation(u, e):
860 raise CodeError("Unknown operation")
862 fun = mapping.get(operation, badOperation)
863 if fun not in (help, ):
864 connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
866 output = fun(u, fields)
867 print 'Content-Type: text/html\n'
868 sys.stderr=sys.stdout
873 output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
875 except CodeError, err:
876 print 'Content-Type: text/html\n'
877 sys.stderr=sys.stdout
880 print error(operation, u, fields, err, e)
881 except InvalidInput, err:
882 print 'Content-Type: text/html\n'
883 sys.stderr=sys.stdout
886 print invalidInput(operation, u, fields, err, e)
888 print 'Content-Type: text/plain\n'
889 sys.stderr=sys.stdout