2 """Main CGI script for web interface"""
19 from StringIO import StringIO
22 def revertStandardError():
23 """Move stderr to stdout, and return the contents of the old stderr."""
25 if not isinstance(errio, StringIO):
27 sys.stderr = sys.stdout
32 """Revert stderr to stdout, and print the contents of stderr"""
33 if isinstance(sys.stderr, StringIO):
34 print revertStandardError()
36 if __name__ == '__main__':
38 atexit.register(printError)
39 sys.stderr = StringIO()
41 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
43 from Cheetah.Template import Template
44 from sipb_xen_database import *
46 class MyException(Exception):
47 """Base class for my exceptions"""
50 class InvalidInput(MyException):
51 """Exception for user-provided input is invalid but maybe in good faith.
53 This would include setting memory to negative (which might be a
54 typo) but not setting an invalid boot CD (which requires bypassing
57 def __init__(self, err_field, err_value, expl=None):
58 MyException.__init__(self, expl)
59 self.err_field = err_field
60 self.err_value = err_value
62 class CodeError(MyException):
63 """Exception for internal errors or bad faith input."""
67 """Return HTML code for a (?) link to a specified help topic"""
68 return ('<span class="helplink"><a href="help?subject=' + subj +
69 '&simple=true" target="_blank" ' +
70 'onclick="return helppopup(\'' + subj + '\')">(?)</a></span>')
73 """Global state of the system, to avoid duplicate remctls to get state"""
74 def __init__(self, user):
77 def __get_uptimes(self):
78 if not hasattr(self, '_uptimes'):
79 self._uptimes = getUptimes(Machine.select())
81 uptimes = property(__get_uptimes)
84 """Clear the state so future accesses reload it."""
85 for attr in ('_uptimes', ):
86 if hasattr(self, attr):
92 """User class (sort of useless, I admit)"""
93 def __init__(self, username, email):
94 self.username = username
97 def makeErrorPre(old, addition):
101 return old[:-6] + '\n----\n' + str(addition) + '</pre>'
103 return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
105 Template.helppopup = staticmethod(helppopup)
109 """Class to store a dictionary that will be converted to JSON"""
110 def __init__(self, **kws):
118 return simplejson.dumps(self.data)
120 def addError(self, text):
121 """Add stderr text to be displayed on the website."""
123 makeErrorPre(self.data.get('err'), text)
126 """Class to store default values for fields."""
132 def __init__(self, max_memory=None, max_disk=None, **kws):
133 if max_memory is not None:
134 self.memory = min(self.memory, max_memory)
135 if max_disk is not None:
136 self.max_disk = min(self.disk, max_disk)
138 setattr(self, key, kws[key])
142 default_headers = {'Content-Type': 'text/html'}
144 # ... and stolen from xend/uuid.py
146 """Generate a random UUID."""
148 return [ random.randint(0, 255) for _ in range(0, 16) ]
151 """Turn a numeric UUID to a hyphen-seperated one."""
152 return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
153 "%02x" * 6]) % tuple(u)
155 MAX_MEMORY_TOTAL = 512
156 MAX_MEMORY_SINGLE = 256
157 MIN_MEMORY_SINGLE = 16
160 MIN_DISK_SINGLE = 0.1
164 def getMachinesByOwner(user, machine=None):
165 """Return the machines owned by the same as a machine.
167 If the machine is None, return the machines owned by the same
171 owner = machine.owner
173 owner = user.username
174 return Machine.select_by(owner=owner)
176 def maxMemory(user, machine=None, on=True):
177 """Return the maximum memory for a machine or a user.
179 If machine is None, return the memory available for a new
180 machine. Else, return the maximum that machine can have.
182 on is whether the machine should be turned on. If false, the max
183 memory for the machine to change to, if it is left off, is
187 return MAX_MEMORY_SINGLE
188 machines = getMachinesByOwner(user, machine)
189 active_machines = [x for x in machines if g.uptimes[x]]
190 mem_usage = sum([x.memory for x in active_machines if x != machine])
191 return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
193 def maxDisk(user, machine=None):
194 machines = getMachinesByOwner(user, machine)
195 disk_usage = sum([sum([y.size for y in x.disks])
196 for x in machines if x != machine])
197 return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
200 machines = getMachinesByOwner(user)
201 active_machines = [x for x in machines if g.uptimes[x]]
202 if len(machines) >= MAX_VMS_TOTAL:
203 return 'You have too many VMs to create a new one.'
204 if len(active_machines) >= MAX_VMS_ACTIVE:
205 return ('You already have the maximum number of VMs turned on. '
206 'To create more, turn one off.')
209 def haveAccess(user, machine):
210 """Return whether a user has adminstrative access to a machine"""
211 if user.username == 'moo':
213 if user.username in (machine.administrator, machine.owner):
215 if getafsgroups.checkAfsGroup(user.username, machine.administrator,
216 'athena.mit.edu'): #XXX Cell?
218 if getafsgroups.checkLockerOwner(user.username, machine.owner):
220 return owns(user, machine)
222 def owns(user, machine):
223 """Return whether a user owns a machine"""
224 if user.username == 'moo':
226 return getafsgroups.checkLockerOwner(user.username, machine.owner)
228 def error(op, user, fields, err, emsg):
229 """Print an error page when a CodeError occurs"""
230 d = dict(op=op, user=user, errorMessage=str(err),
232 return Template(file='error.tmpl', searchList=[d]);
234 def invalidInput(op, user, fields, err, emsg):
235 """Print an error page when an InvalidInput exception occurs"""
236 d = dict(op=op, user=user, err_field=err.err_field,
237 err_value=str(err.err_value), stderr=emsg,
238 errorMessage=str(err))
239 return Template(file='invalid.tmpl', searchList=[d]);
241 def validMachineName(name):
242 """Check that name is valid for a machine name"""
245 charset = string.ascii_letters + string.digits + '-_'
246 if name[0] in '-_' or len(name) > 22:
253 def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
254 """Kinit with a given username and keytab"""
256 p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
257 stderr=subprocess.PIPE)
260 raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
263 """If we lack tickets, kinit."""
264 p = subprocess.Popen(['klist', '-s'])
268 def remctl(*args, **kws):
269 """Perform a remctl and return the output.
271 kinits if necessary, and outputs errors to stderr.
274 p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
276 stdout=subprocess.PIPE,
277 stderr=subprocess.PIPE)
280 return p.stdout.read(), p.stderr.read()
282 print >> sys.stderr, 'Error', v, 'on remctl', args, ':'
283 print >> sys.stderr, p.stderr.read()
284 raise CodeError('ERROR on remctl')
285 return p.stdout.read()
287 def lvcreate(machine, disk):
288 """Create a single disk for a machine"""
289 remctl('web', 'lvcreate', machine.name,
290 disk.guest_device_name, str(disk.size))
292 def makeDisks(machine):
293 """Update the lvm partitions to add a disk."""
294 for disk in machine.disks:
295 lvcreate(machine, disk)
297 def bootMachine(machine, cdtype):
298 """Boot a machine with a given boot CD.
300 If cdtype is None, give no boot cd. Otherwise, it is the string
301 id of the CD (e.g. 'gutsy_i386')
303 if cdtype is not None:
304 remctl('control', machine.name, 'create',
307 remctl('control', machine.name, 'create')
309 def registerMachine(machine):
310 """Register a machine to be controlled by the web interface"""
311 remctl('web', 'register', machine.name)
313 def unregisterMachine(machine):
314 """Unregister a machine to not be controlled by the web interface"""
315 remctl('web', 'unregister', machine.name)
318 """Parse a status string into nested tuples of strings.
320 s = output of xm list --long <machine_name>
322 values = re.split('([()])', s)
324 for v in values[2:-2]: #remove initial and final '()'
331 if len(stack[-1]) == 1:
333 stack[-2].append(stack[-1])
338 stack[-1].extend(v.split())
341 def getUptimes(machines=None):
342 """Return a dictionary mapping machine names to uptime strings"""
343 value_string = remctl('web', 'listvms')
344 lines = value_string.splitlines()
349 uptime = ' '.join(lst[2:])
353 ans[m] = d.get(m.name)
356 def statusInfo(machine):
357 """Return the status list for a given machine.
359 Gets and parses xm list --long
361 value_string, err_string = remctl('control', machine.name, 'list-long',
363 if 'Unknown command' in err_string:
364 raise CodeError("ERROR in remctl list-long %s is not registered" %
366 elif 'does not exist' in err_string:
369 raise CodeError("ERROR in remctl list-long %s: %s" %
370 (machine.name, err_string))
371 status = parseStatus(value_string)
375 """Does the machine with a given status list support VNC?"""
379 if l[0] == 'device' and l[1][0] == 'vfb':
381 return 'location' in d
384 def createVm(user, name, memory, disk, is_hvm, cdrom):
385 """Create a VM and put it in the database"""
386 # put stuff in the table
387 transaction = ctx.current.create_transaction()
389 if memory > maxMemory(user):
390 raise InvalidInput('memory', memory,
391 "Max %s" % maxMemory(user))
392 if disk > maxDisk(user) * 1024:
393 raise InvalidInput('disk', disk,
394 "Max %s" % maxDisk(user))
395 reason = cantAddVm(user)
397 raise InvalidInput('create', True, reason)
398 res = meta.engine.execute('select nextval('
399 '\'"machines_machine_id_seq"\')')
400 id = res.fetchone()[0]
402 machine.machine_id = id
404 machine.memory = memory
405 machine.owner = user.username
406 machine.administrator = user.username
407 machine.contact = user.email
408 machine.uuid = uuidToString(randomUUID())
409 machine.boot_off_cd = True
410 machine_type = Type.get_by(hvm=is_hvm)
411 machine.type_id = machine_type.type_id
412 ctx.current.save(machine)
413 disk = Disk(machine.machine_id,
415 open_nics = NIC.select_by(machine_id=None)
416 if not open_nics: #No IPs left!
417 raise CodeError("No IP addresses left! "
418 "Contact sipb-xen-dev@mit.edu")
420 nic.machine_id = machine.machine_id
422 ctx.current.save(nic)
423 ctx.current.save(disk)
426 transaction.rollback()
428 registerMachine(machine)
430 # tell it to boot with cdrom
431 bootMachine(machine, cdrom)
435 def validMemory(user, memory, machine=None, on=True):
436 """Parse and validate limits for memory for a given user and machine.
438 on is whether the memory must be valid after the machine is
443 if memory < MIN_MEMORY_SINGLE:
446 raise InvalidInput('memory', memory,
447 "Minimum %s MB" % MIN_MEMORY_SINGLE)
448 if memory > maxMemory(user, machine, on):
449 raise InvalidInput('memory', memory,
450 'Maximum %s MB' % maxMemory(user, machine))
453 def validDisk(user, disk, machine=None):
454 """Parse and validate limits for disk for a given user and machine."""
457 if disk > maxDisk(user, machine):
458 raise InvalidInput('disk', disk,
459 "Maximum %s G" % maxDisk(user, machine))
460 disk = int(disk * 1024)
461 if disk < MIN_DISK_SINGLE * 1024:
464 raise InvalidInput('disk', disk,
465 "Minimum %s GB" % MIN_DISK_SINGLE)
468 def parseCreate(user, fields):
469 name = fields.getfirst('name')
470 if not validMachineName(name):
471 raise InvalidInput('name', name, 'You must provide a machine name.')
474 if Machine.get_by(name=name):
475 raise InvalidInput('name', name,
476 "Name already exists.")
478 memory = fields.getfirst('memory')
479 memory = validMemory(user, memory, on=True)
481 disk = fields.getfirst('disk')
482 disk = validDisk(user, disk)
484 vm_type = fields.getfirst('vmtype')
485 if vm_type not in ('hvm', 'paravm'):
486 raise CodeError("Invalid vm type '%s'" % vm_type)
487 is_hvm = (vm_type == 'hvm')
489 cdrom = fields.getfirst('cdrom')
490 if cdrom is not None and not CDROM.get(cdrom):
491 raise CodeError("Invalid cdrom type '%s'" % cdrom)
492 return dict(user=user, name=name, memory=memory, disk=disk,
493 is_hvm=is_hvm, cdrom=cdrom)
495 def create(user, fields):
496 """Handler for create requests."""
498 parsed_fields = parseCreate(user, fields)
499 machine = createVm(**parsed_fields)
500 except InvalidInput, err:
504 g.clear() #Changed global state
505 d = getListDict(user)
508 for field in fields.keys():
509 setattr(d['defaults'], field, fields.getfirst(field))
511 d['new_machine'] = parsed_fields['name']
512 return Template(file='list.tmpl', searchList=[d])
515 def getListDict(user):
516 machines = [m for m in Machine.select() if haveAccess(user, m)]
521 m.uptime = g.uptimes.get(m)
527 has_vnc[m] = "ParaVM"+helppopup("paravm_console")
529 # status = statusInfo(m)
530 # on[m.name] = status is not None
531 # has_vnc[m.name] = hasVnc(status)
532 max_memory = maxMemory(user)
533 max_disk = maxDisk(user)
534 defaults = Defaults(max_memory=max_memory,
538 cant_add_vm=cantAddVm(user),
539 max_memory=max_memory,
545 cdroms=CDROM.select())
548 def listVms(user, fields):
549 """Handler for list requests."""
550 d = getListDict(user)
551 return Template(file='list.tmpl', searchList=[d])
553 def testMachineId(user, machineId, exists=True):
554 """Parse, validate and check authorization for a given machineId.
556 If exists is False, don't check that it exists.
558 if machineId is None:
559 raise CodeError("No machine ID specified")
561 machineId = int(machineId)
563 raise CodeError("Invalid machine ID '%s'" % machineId)
564 machine = Machine.get(machineId)
565 if exists and machine is None:
566 raise CodeError("No such machine ID '%s'" % machineId)
567 if machine is not None and not haveAccess(user, machine):
568 raise CodeError("No access to machine ID '%s'" % machineId)
571 def vnc(user, fields):
574 Note that due to same-domain restrictions, the applet connects to
575 the webserver, which needs to forward those requests to the xen
576 server. The Xen server runs another proxy that (1) authenticates
577 and (2) finds the correct port for the VM.
579 You might want iptables like:
581 -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
582 --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
583 -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
584 --dport 10003 -j SNAT --to-source 18.187.7.142
585 -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
586 --dport 10003 -j ACCEPT
588 Remember to enable iptables!
589 echo 1 > /proc/sys/net/ipv4/ip_forward
591 machine = testMachineId(user, fields.getfirst('machine_id'))
593 TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
596 data["user"] = user.username
597 data["machine"] = machine.name
598 data["expires"] = time.time()+(5*60)
599 pickled_data = cPickle.dumps(data)
600 m = hmac.new(TOKEN_KEY, digestmod=sha)
601 m.update(pickled_data)
602 token = {'data': pickled_data, 'digest': m.digest()}
603 token = cPickle.dumps(token)
604 token = base64.urlsafe_b64encode(token)
606 status = statusInfo(machine)
607 has_vnc = hasVnc(status)
613 hostname=os.environ.get('SERVER_NAME', 'localhost'),
615 return Template(file='vnc.tmpl', searchList=[d])
617 def getNicInfo(data_dict, machine):
618 """Helper function for info, get data on nics for a machine.
620 Modifies data_dict to include the relevant data, and returns a list
621 of (key, name) pairs to display "name: data_dict[key]" to the user.
623 data_dict['num_nics'] = len(machine.nics)
624 nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
625 ('nic%s_mac', 'NIC %s MAC Addr'),
626 ('nic%s_ip', 'NIC %s IP'),
629 for i in range(len(machine.nics)):
630 nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
631 data_dict['nic%s_hostname' % i] = (machine.nics[i].hostname +
632 '.servers.csail.mit.edu')
633 data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
634 data_dict['nic%s_ip' % i] = machine.nics[i].ip
635 if len(machine.nics) == 1:
636 nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
639 def getDiskInfo(data_dict, machine):
640 """Helper function for info, get data on disks for a machine.
642 Modifies data_dict to include the relevant data, and returns a list
643 of (key, name) pairs to display "name: data_dict[key]" to the user.
645 data_dict['num_disks'] = len(machine.disks)
646 disk_fields_template = [('%s_size', '%s size')]
648 for disk in machine.disks:
649 name = disk.guest_device_name
650 disk_fields.extend([(x % name, y % name) for x, y in
651 disk_fields_template])
652 data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
655 def deleteVM(machine):
657 remctl('control', machine.name, 'destroy', err=True)
658 transaction = ctx.current.create_transaction()
659 delete_disk_pairs = [(machine.name, d.guest_device_name)
660 for d in machine.disks]
662 for nic in machine.nics:
663 nic.machine_id = None
665 ctx.current.save(nic)
666 for disk in machine.disks:
667 ctx.current.delete(disk)
668 ctx.current.delete(machine)
671 transaction.rollback()
673 for mname, dname in delete_disk_pairs:
674 remctl('web', 'lvremove', mname, dname)
675 unregisterMachine(machine)
677 def commandResult(user, fields):
678 print >> sys.stderr, time.time()-start_time
679 machine = testMachineId(user, fields.getfirst('machine_id'))
680 action = fields.getfirst('action')
681 cdrom = fields.getfirst('cdrom')
682 print >> sys.stderr, time.time()-start_time
683 if cdrom is not None and not CDROM.get(cdrom):
684 raise CodeError("Invalid cdrom type '%s'" % cdrom)
685 if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown',
687 raise CodeError("Invalid action '%s'" % action)
688 if action == 'Reboot':
689 if cdrom is not None:
690 out, err = remctl('control', machine.name, 'reboot', cdrom,
693 out, err = remctl('control', machine.name, 'reboot',
696 if re.match("Error: Domain '.*' does not exist.", err):
697 raise InvalidInput("action", "reboot",
700 print >> sys.stderr, 'Error on reboot:'
701 print >> sys.stderr, err
702 raise CodeError('ERROR on remctl')
704 elif action == 'Power on':
705 if maxMemory(user) < machine.memory:
706 raise InvalidInput('action', 'Power on',
707 "You don't have enough free RAM quota "
708 "to turn on this machine.")
709 bootMachine(machine, cdrom)
710 elif action == 'Power off':
711 out, err = remctl('control', machine.name, 'destroy', err=True)
713 if re.match("Error: Domain '.*' does not exist.", err):
714 raise InvalidInput("action", "Power off",
715 "Machine is not on.")
717 print >> sys.stderr, 'Error on power off:'
718 print >> sys.stderr, err
719 raise CodeError('ERROR on remctl')
720 elif action == 'Shutdown':
721 out, err = remctl('control', machine.name, 'shutdown', err=True)
723 if re.match("Error: Domain '.*' does not exist.", err):
724 raise InvalidInput("action", "Shutdown",
725 "Machine is not on.")
727 print >> sys.stderr, 'Error on Shutdown:'
728 print >> sys.stderr, err
729 raise CodeError('ERROR on remctl')
730 elif action == 'Delete VM':
732 print >> sys.stderr, time.time()-start_time
739 def command(user, fields):
740 """Handler for running commands like boot and delete on a VM."""
741 back = fields.getfirst('back')
743 d = commandResult(user, fields)
744 if d['command'] == 'Delete VM':
746 except InvalidInput, err:
749 print >> sys.stderr, err
754 return Template(file='command.tmpl', searchList=[d])
756 g.clear() #Changed global state
757 d = getListDict(user)
759 return Template(file='list.tmpl', searchList=[d])
761 machine = testMachineId(user, fields.getfirst('machine_id'))
762 d = infoDict(user, machine)
764 return Template(file='info.tmpl', searchList=[d])
766 raise InvalidInput('back', back, 'Not a known back page.')
768 def testAdmin(user, admin, machine):
769 if admin in (None, machine.administrator):
771 if admin == user.username:
773 if getafsgroups.checkAfsGroup(user.username, admin, 'athena.mit.edu'):
775 if getafsgroups.checkAfsGroup(user.username, 'system:'+admin,
777 return 'system:'+admin
779 #raise InvalidInput('administrator', admin,
780 # 'You must control the group you move it to.')
782 def testOwner(user, owner, machine):
783 if owner in (None, machine.owner):
785 value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
788 raise InvalidInput('owner', owner, value)
790 def testContact(user, contact, machine=None):
791 if contact in (None, machine.contact):
793 if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
794 raise InvalidInput('contact', contact, "Not a valid email.")
797 def testDisk(user, disksize, machine=None):
800 def testName(user, name, machine=None):
801 if name in (None, machine.name):
803 if not Machine.select_by(name=name):
805 raise InvalidInput('name', name, "Name is already taken.")
807 def testHostname(user, hostname, machine):
808 for nic in machine.nics:
809 if hostname == nic.hostname:
811 # check if doesn't already exist
812 if NIC.select_by(hostname=hostname):
813 raise InvalidInput('hostname', hostname,
815 if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
816 raise InvalidInput('hostname', hostname, "Not a valid hostname; "
817 "must only use number, letters, and dashes.")
820 def modifyDict(user, fields):
822 transaction = ctx.current.create_transaction()
824 machine = testMachineId(user, fields.getfirst('machine_id'))
825 owner = testOwner(user, fields.getfirst('owner'), machine)
826 admin = testAdmin(user, fields.getfirst('administrator'), machine)
827 contact = testContact(user, fields.getfirst('contact'), machine)
828 hostname = testHostname(owner, fields.getfirst('hostname'), machine)
829 name = testName(user, fields.getfirst('name'), machine)
830 oldname = machine.name
833 memory = fields.getfirst('memory')
834 if memory is not None:
835 memory = validMemory(user, memory, machine, on=False)
836 machine.memory = memory
838 disksize = testDisk(user, fields.getfirst('disk'))
839 if disksize is not None:
840 disksize = validDisk(user, disksize, machine)
841 disk = machine.disks[0]
842 if disk.size != disksize:
843 olddisk[disk.guest_device_name] = disksize
845 ctx.current.save(disk)
847 # XXX first NIC gets hostname on change?
848 # Interface doesn't support more.
849 for nic in machine.nics[:1]:
850 nic.hostname = hostname
851 ctx.current.save(nic)
853 if owner is not None:
854 machine.owner = owner
857 if admin is not None:
858 machine.administrator = admin
859 if contact is not None:
860 machine.contact = contact
862 ctx.current.save(machine)
865 transaction.rollback()
867 for diskname in olddisk:
868 remctl("web", "lvresize", oldname, diskname, str(olddisk[diskname]))
870 for disk in machine.disks:
871 remctl("web", "lvrename", oldname, disk.guest_device_name, name)
872 remctl("web", "moveregister", oldname, name)
873 return dict(user=user,
877 def modify(user, fields):
878 """Handler for modifying attributes of a machine."""
880 modify_dict = modifyDict(user, fields)
881 except InvalidInput, err:
883 machine = testMachineId(user, fields.getfirst('machine_id'))
885 machine = modify_dict['machine']
888 info_dict = infoDict(user, machine)
889 info_dict['err'] = err
891 for field in fields.keys():
892 setattr(info_dict['defaults'], field, fields.getfirst(field))
893 info_dict['result'] = result
894 return Template(file='info.tmpl', searchList=[info_dict])
897 def helpHandler(user, fields):
898 """Handler for help messages."""
899 simple = fields.getfirst('simple')
900 subjects = fields.getlist('subject')
902 help_mapping = dict(paravm_console="""
903 ParaVM machines do not support console access over VNC. To access
904 these machines, you either need to boot with a liveCD and ssh in or
905 hope that the sipb-xen maintainers add support for serial consoles.""",
907 HVM machines use the virtualization features of the processor, while
908 ParaVM machines use Xen's emulation of virtualization features. You
909 want an HVM virtualized machine.""",
911 Don't ask us! We're as mystified as you are.""",
913 The owner field is used to determine <a
914 href="help?subject=quotas">quotas</a>. It must be the name of a
915 locker that you are an AFS administrator of. In particular, you or an
916 AFS group you are a member of must have AFS rlidwka bits on the
917 locker. You can check see who administers the LOCKER locker using the
918 command 'fs la /mit/LOCKER' on Athena.) See also <a
919 href="help?subject=administrator">administrator</a>.""",
921 The administrator field determines who can access the console and
922 power on and off the machine. This can be either a user or a moira
925 Quotas are determined on a per-locker basis. Each quota may have a
926 maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
931 subjects = sorted(help_mapping.keys())
936 mapping=help_mapping)
938 return Template(file="help.tmpl", searchList=[d])
941 def badOperation(u, e):
942 raise CodeError("Unknown operation")
944 def infoDict(user, machine):
945 status = statusInfo(machine)
946 has_vnc = hasVnc(status)
948 main_status = dict(name=machine.name,
949 memory=str(machine.memory))
953 main_status = dict(status[1:])
954 start_time = float(main_status.get('start_time', 0))
955 uptime = datetime.timedelta(seconds=int(time.time()-start_time))
956 cpu_time_float = float(main_status.get('cpu_time', 0))
957 cputime = datetime.timedelta(seconds=int(cpu_time_float))
958 display_fields = """name uptime memory state cpu_weight on_reboot
959 on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
960 display_fields = [('name', 'Name'),
962 ('administrator', 'Administrator'),
963 ('contact', 'Contact'),
966 ('uptime', 'uptime'),
967 ('cputime', 'CPU usage'),
970 ('state', 'state (xen format)'),
971 ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
972 ('on_reboot', 'Action on VM reboot'),
973 ('on_poweroff', 'Action on VM poweroff'),
974 ('on_crash', 'Action on VM crash'),
975 ('on_xend_start', 'Action on Xen start'),
976 ('on_xend_stop', 'Action on Xen stop'),
977 ('bootloader', 'Bootloader options'),
981 machine_info['name'] = machine.name
982 machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
983 machine_info['owner'] = machine.owner
984 machine_info['administrator'] = machine.administrator
985 machine_info['contact'] = machine.contact
987 nic_fields = getNicInfo(machine_info, machine)
988 nic_point = display_fields.index('NIC_INFO')
989 display_fields = (display_fields[:nic_point] + nic_fields +
990 display_fields[nic_point+1:])
992 disk_fields = getDiskInfo(machine_info, machine)
993 disk_point = display_fields.index('DISK_INFO')
994 display_fields = (display_fields[:disk_point] + disk_fields +
995 display_fields[disk_point+1:])
997 main_status['memory'] += ' MB'
998 for field, disp in display_fields:
999 if field in ('uptime', 'cputime') and locals()[field] is not None:
1000 fields.append((disp, locals()[field]))
1001 elif field in machine_info:
1002 fields.append((disp, machine_info[field]))
1003 elif field in main_status:
1004 fields.append((disp, main_status[field]))
1007 #fields.append((disp, None))
1008 max_mem = maxMemory(user, machine)
1009 max_disk = maxDisk(user, machine)
1011 for name in 'machine_id name administrator owner memory contact'.split():
1012 setattr(defaults, name, getattr(machine, name))
1014 defaults.hostname = machine.nics[0].hostname
1015 defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
1017 cdroms=CDROM.select(),
1018 on=status is not None,
1026 owner_help=helppopup("owner"),
1030 def info(user, fields):
1031 """Handler for info on a single VM."""
1032 machine = testMachineId(user, fields.getfirst('machine_id'))
1033 d = infoDict(user, machine)
1034 return Template(file='info.tmpl', searchList=[d])
1036 mapping = dict(list=listVms,
1044 def printHeaders(headers):
1045 for key, value in headers.iteritems():
1046 print '%s: %s' % (key, value)
1051 """Return the current user based on the SSL environment variables"""
1052 if 'SSL_CLIENT_S_DN_Email' in os.environ:
1053 username = os.environ['SSL_CLIENT_S_DN_Email'].split("@")[0]
1054 return User(username, os.environ['SSL_CLIENT_S_DN_Email'])
1056 return User('moo', 'nobody')
1058 if __name__ == '__main__':
1059 start_time = time.time()
1060 fields = cgi.FieldStorage()
1063 operation = os.environ.get('PATH_INFO', '')
1065 print "Status: 301 Moved Permanently"
1066 print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
1069 if operation.startswith('/'):
1070 operation = operation[1:]
1076 fun = mapping.get(operation, badOperation)
1078 if fun not in (helpHandler, ):
1079 connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
1081 output = fun(u, fields)
1083 headers = dict(default_headers)
1084 if isinstance(output, tuple):
1085 new_headers, output = output
1086 headers.update(new_headers)
1088 e = revertStandardError()
1091 printHeaders(headers)
1093 except Exception, err:
1094 if not fields.has_key('js'):
1095 if isinstance(err, CodeError):
1096 print 'Content-Type: text/html\n'
1097 e = revertStandardError()
1098 print error(operation, u, fields, err, e)
1100 if isinstance(err, InvalidInput):
1101 print 'Content-Type: text/html\n'
1102 e = revertStandardError()
1103 print invalidInput(operation, u, fields, err, e)
1105 print 'Content-Type: text/plain\n'
1106 print 'Uh-oh! We experienced an error.'
1107 print 'Please email sipb-xen@mit.edu with the contents of this page.'
1109 e = revertStandardError()