X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-web.git/blobdiff_plain/bc2c23e470e232b2a87cad3c826fcf4c07dc9d62..3d1cc19a7889d4ac32b00bbddb142755dd4bb24a:/templates/main.py diff --git a/templates/main.py b/templates/main.py index 984f3b8..09cc0ac 100755 --- a/templates/main.py +++ b/templates/main.py @@ -12,8 +12,9 @@ import base64 import sha import hmac import datetime +import StringIO -sys.stderr = sys.stdout +sys.stderr = StringIO.StringIO() sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages') from Cheetah.Template import Template @@ -31,13 +32,26 @@ class InvalidInput(MyException): typo) but not setting an invalid boot CD (which requires bypassing the select box). """ - pass + def __init__(self, err_field, err_value, expl=None): + super(InvalidInput, self).__init__(expl) + self.err_field = err_field + self.err_value = err_value class CodeError(MyException): """Exception for internal errors or bad faith input.""" pass +class Global(object): + def __init__(self, user): + self.user = user + + def __get_uptimes(self): + if not hasattr(self, '_uptimes'): + self._uptimes = getUptimes(self.machines) + return self._uptimes + uptimes = property(__get_uptimes) +g = None def helppopup(subj): """Return HTML code for a (?) link to a specified help topic""" @@ -72,7 +86,7 @@ def getMachinesByOwner(owner): """Return the machines owned by a given owner.""" return Machine.select_by(owner=owner) -def maxMemory(user, machine=None, on=None): +def maxMemory(user, machine=None): """Return the maximum memory for a machine or a user. If machine is None, return the memory available for a new @@ -83,9 +97,7 @@ def maxMemory(user, machine=None, on=None): """ machines = getMachinesByOwner(user.username) - if on is None: - on = getUptimes(machines) - active_machines = [x for x in machines if on[x]] + active_machines = [x for x in machines if g.uptimes[x]] mem_usage = sum([x.memory for x in active_machines if x != machine]) return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage) @@ -95,11 +107,9 @@ def maxDisk(user, machine=None): for x in machines if x != machine]) return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.) -def canAddVm(user, on=None): +def canAddVm(user): machines = getMachinesByOwner(user.username) - if on is None: - on = getUptimes(machines) - active_machines = [x for x in machines if on[x]] + active_machines = [x for x in machines if g.uptimes[x]] return (len(machines) < MAX_VMS_TOTAL and len(active_machines) < MAX_VMS_ACTIVE) @@ -109,10 +119,18 @@ def haveAccess(user, machine): return True return machine.owner == user.username -def error(op, user, fields, err): +def error(op, user, fields, err, emsg): """Print an error page when a CodeError occurs""" - d = dict(op=op, user=user, errorMessage=str(err)) - print Template(file='error.tmpl', searchList=[d, global_dict]); + d = dict(op=op, user=user, errorMessage=str(err), + stderr=emsg) + return Template(file='error.tmpl', searchList=[d, global_dict]); + +def invalidInput(op, user, fields, err, emsg): + """Print an error page when an InvalidInput exception occurs""" + d = dict(op=op, user=user, err_field=err.err_field, + err_value=str(err.err_value), stderr=emsg, + errorMessage=str(err)) + return Template(file='invalid.tmpl', searchList=[d, global_dict]); def validMachineName(name): """Check that name is valid for a machine name""" @@ -159,9 +177,15 @@ def remctl(*args, **kws): (args, p.stderr.read())) return p.stdout.read() -def makeDisks(): - """Update the lvm partitions to include all disks in the database.""" - remctl('web', 'lvcreate') +def lvcreate(machine, disk): + """Create a single disk for a machine""" + remctl('web', 'lvcreate', machine.name, + disk.guest_device_name, str(disk.size)) + +def makeDisks(machine): + """Update the lvm partitions to add a disk.""" + for disk in machine.disks: + lvcreate(machine, disk) def bootMachine(machine, cdtype): """Boot a machine with a given boot CD. @@ -253,11 +277,13 @@ def createVm(user, name, memory, disk, is_hvm, cdrom): transaction = ctx.current.create_transaction() try: if memory > maxMemory(user): - raise InvalidInput("Too much memory requested") + raise InvalidInput('memory', memory, + "Max %s" % maxMemory(user)) if disk > maxDisk(user) * 1024: - raise InvalidInput("Too much disk requested") + raise InvalidInput('disk', disk, + "Max %s" % maxDisk(user)) if not canAddVm(user): - raise InvalidInput("Too many VMs requested") + raise InvalidInput('create', True, 'Unable to create more VMs') res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')') id = res.fetchone()[0] machine = Machine() @@ -286,7 +312,7 @@ def createVm(user, name, memory, disk, is_hvm, cdrom): transaction.rollback() raise registerMachine(machine) - makeDisks() + makeDisks(machine) # tell it to boot with cdrom bootMachine(machine, cdrom) @@ -299,10 +325,11 @@ def validMemory(user, memory, machine=None): if memory < MIN_MEMORY_SINGLE: raise ValueError except ValueError: - raise InvalidInput("Invalid memory amount; must be at least %s MB" % - MIN_MEMORY_SINGLE) + raise InvalidInput('memory', memory, + "Minimum %s MB" % MIN_MEMORY_SINGLE) if memory > maxMemory(user, machine): - raise InvalidInput("Too much memory requested") + raise InvalidInput('memory', memory, + 'Maximum %s MB' % maxMemory(user, machine)) return memory def validDisk(user, disk, machine=None): @@ -310,24 +337,26 @@ def validDisk(user, disk, machine=None): try: disk = float(disk) if disk > maxDisk(user, machine): - raise InvalidInput("Too much disk requested") + raise InvalidInput('disk', disk, + "Maximum %s G" % maxDisk(user, machine)) disk = int(disk * 1024) if disk < MIN_DISK_SINGLE * 1024: raise ValueError except ValueError: - raise InvalidInput("Invalid disk amount; minimum is %s GB" % - MIN_DISK_SINGLE) + raise InvalidInput('disk', disk, + "Minimum %s GB" % MIN_DISK_SINGLE) return disk def create(user, fields): """Handler for create requests.""" name = fields.getfirst('name') if not validMachineName(name): - raise InvalidInput("Invalid name '%s'" % name) + raise InvalidInput('name', name) name = user.username + '_' + name.lower() if Machine.get_by(name=name): - raise InvalidInput("A machine named '%s' already exists" % name) + raise InvalidInput('name', name, + "Already exists") memory = fields.getfirst('memory') memory = validMemory(user, memory) @@ -347,7 +376,7 @@ def create(user, fields): machine = createVm(user, name, memory, disk, is_hvm, cdrom) d = dict(user=user, machine=machine) - print Template(file='create.tmpl', + return Template(file='create.tmpl', searchList=[d, global_dict]); def listVms(user, fields): @@ -355,8 +384,7 @@ def listVms(user, fields): machines = [m for m in Machine.select() if haveAccess(user, m)] on = {} has_vnc = {} - uptimes = getUptimes(machines) - on = uptimes + on = g.uptimes for m in machines: if not on[m]: has_vnc[m] = 'Off' @@ -368,10 +396,10 @@ def listVms(user, fields): # status = statusInfo(m) # on[m.name] = status is not None # has_vnc[m.name] = hasVnc(status) - max_mem=maxMemory(user, on=on) + max_mem=maxMemory(user) max_disk=maxDisk(user) d = dict(user=user, - can_add_vm=canAddVm(user, on=on), + can_add_vm=canAddVm(user), max_mem=max_mem, max_disk=max_disk, default_mem=max_mem, @@ -380,7 +408,7 @@ def listVms(user, fields): has_vnc=has_vnc, uptimes=uptimes, cdroms=CDROM.select()) - print Template(file='list.tmpl', searchList=[d, global_dict]) + return Template(file='list.tmpl', searchList=[d, global_dict]) def testMachineId(user, machineId, exists=True): """Parse, validate and check authorization for a given machineId. @@ -432,11 +460,16 @@ def vnc(user, fields): token = cPickle.dumps(token) token = base64.urlsafe_b64encode(token) + status = statusInfo(machine) + has_vnc = hasVnc(status) + d = dict(user=user, + on=status, + has_vnc=has_vnc, machine=machine, hostname=os.environ.get('SERVER_NAME', 'localhost'), authtoken=token) - print Template(file='vnc.tmpl', + return Template(file='vnc.tmpl', searchList=[d, global_dict]) def getNicInfo(data_dict, machine): @@ -513,7 +546,8 @@ def command(user, fields): remctl('reboot', machine.name) elif action == 'Power on': if maxMemory(user) < machine.memory: - raise InvalidInput("You don't have enough free RAM quota") + raise InvalidInput('action', 'Power on', + "You don't have enough free RAM quota to turn on this machine") bootMachine(machine, cdrom) elif action == 'Power off': remctl('destroy', machine.name) @@ -526,13 +560,45 @@ def command(user, fields): d = dict(user=user, command=action, machine=machine) - print Template(file="command.tmpl", searchList=[d, global_dict]) - + return Template(file="command.tmpl", searchList=[d, global_dict]) + +def testOwner(user, owner, machine=None): + if owner != user.username: + raise InvalidInput('owner', owner, + "Invalid") + return owner + +def testContact(user, contact, machine=None): + if contact != user.email: + raise InvalidInput('contact', contact, + "Invalid") + return contact + +def testHostname(user, hostname, machine): + for nic in machine.nics: + if hostname == nic.hostname: + return hostname + raise InvalidInput('hostname', hostname, + "Different from before") + + def modify(user, fields): """Handler for modifying attributes of a machine.""" #XXX not written yet machine = testMachineId(user, fields.getfirst('machine_id')) + owner = testOwner(user, fields.getfirst('owner'), machine) + contact = testContact(user, fields.getfirst('contact')) + hostname = testHostname(user, fields.getfirst('hostname'), + machine) + ram = fields.getfirst('memory') + if ram is not None: + ram = validMemory(user, ram, machine) + disk = testDisk(user, fields.getfirst('disk')) + if disk is not None: + disk = validDisk(user, disk, machine) + + def help(user, fields): """Handler for help messages.""" simple = fields.getfirst('simple') @@ -553,7 +619,7 @@ want an HVM virtualized machine.""", subjects=subjects, mapping=mapping) - print Template(file="help.tmpl", searchList=[d, global_dict]) + return Template(file="help.tmpl", searchList=[d, global_dict]) def info(user, fields): @@ -628,7 +694,7 @@ def info(user, fields): max_mem=max_mem, max_disk=max_disk, fields = fields) - print Template(file='info.tmpl', + return Template(file='info.tmpl', searchList=[d, global_dict]) mapping = dict(list=listVms, @@ -646,6 +712,7 @@ if __name__ == '__main__': username = "moo" email = 'moo@cow.com' u = User() + g = Global(u) if 'SSL_CLIENT_S_DN_Email' in os.environ: username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0] u.username = username @@ -661,22 +728,40 @@ if __name__ == '__main__': print "Status: 301 Moved Permanently" print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n' sys.exit(0) - print 'Content-Type: text/html\n' if operation.startswith('/'): operation = operation[1:] if not operation: operation = 'list' - fun = mapping.get(operation, - lambda u, e: - error(operation, u, e, - "Invalid operation '%s'" % operation)) + def badOperation(u, e): + raise CodeError("Unknown operation") + + fun = mapping.get(operation, badOperation) if fun not in (help, ): connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen') try: - fun(u, fields) + output = fun(u, fields) + print 'Content-Type: text/html\n' + sys.stderr.seek(0) + e = sys.stderr.read() + if e: + output = output.replace('', '
'+e+'
') + print output except CodeError, err: - error(operation, u, fields, err) + print 'Content-Type: text/html\n' + sys.stderr.seek(0) + e = sys.stderr.read() + print error(operation, u, fields, err, e) except InvalidInput, err: - error(operation, u, fields, err) + print 'Content-Type: text/html\n' + sys.stderr.seek(0) + e = sys.stderr.read() + print invalidInput(operation, u, fields, err, e) + except: + print 'Content-Type: text/plain\n' + sys.stderr.seek(0) + e = sys.stderr.read() + print e + print '----' + raise