X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-web.git/blobdiff_plain/941f0ff1651464e357189bdc7db1e95469653939..7268d3236dc1ea69c8ff786e43ea77939c6d1a97:/templates/main.py diff --git a/templates/main.py b/templates/main.py index 8de8a37..36ea239 100755 --- a/templates/main.py +++ b/templates/main.py @@ -12,8 +12,10 @@ import base64 import sha import hmac import datetime +import StringIO +import getafsgroups -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,7 +33,10 @@ 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.""" @@ -43,7 +48,7 @@ class Global(object): def __get_uptimes(self): if not hasattr(self, '_uptimes'): - self._uptimes = getUptimes(self.machines) + self._uptimes = getUptimes(Machine.select()) return self._uptimes uptimes = property(__get_uptimes) @@ -113,12 +118,20 @@ def haveAccess(user, machine): """Return whether a user has access to a machine""" if user.username == 'moo': return True - return machine.owner == user.username + return getafsgroups.checkLockerOwner(user.username,machine.owner) -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""" @@ -219,7 +232,7 @@ def parseStatus(s): stack[-1].extend(v.split()) return stack[-1] -def getUptimes(machines): +def getUptimes(machines=None): """Return a dictionary mapping machine names to uptime strings""" value_string = remctl('web', 'listvms') lines = value_string.splitlines() @@ -265,11 +278,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() @@ -311,10 +326,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): @@ -322,24 +338,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) - name = user.username + '_' + name.lower() + raise InvalidInput('name', name) + name = 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) @@ -359,7 +377,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): @@ -389,9 +407,9 @@ def listVms(user, fields): default_disk=min(4.0, max_disk), machines=machines, has_vnc=has_vnc, - uptimes=uptimes, + uptimes=g.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. @@ -452,7 +470,7 @@ def vnc(user, fields): 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): @@ -493,6 +511,10 @@ def getDiskInfo(data_dict, machine): def deleteVM(machine): """Delete a VM.""" + try: + remctl('destroy', machine.name) + except: + pass transaction = ctx.current.create_transaction() delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks] try: @@ -513,11 +535,11 @@ def deleteVM(machine): def command(user, fields): """Handler for running commands like boot and delete on a VM.""" - print time.time()-start_time + print >> sys.stderr, time.time()-start_time machine = testMachineId(user, fields.getfirst('machine_id')) action = fields.getfirst('action') cdrom = fields.getfirst('cdrom') - print time.time()-start_time + print >> sys.stderr, time.time()-start_time if cdrom is not None and not CDROM.get(cdrom): raise CodeError("Invalid cdrom type '%s'" % cdrom) if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'): @@ -529,7 +551,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) @@ -537,18 +560,99 @@ def command(user, fields): remctl('shutdown', machine.name) elif action == 'Delete VM': deleteVM(machine) - print time.time()-start_time + print >> sys.stderr, time.time()-start_time 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 not getafsgroups.checkLockerOwner(user.username, owner): + 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 testDisk(user, disksize, machine=None): + return disksize + +def testName(user, name, machine=None): + return name + +def testHostname(user, hostname, machine): + for nic in machine.nics: + if hostname == nic.hostname: + return hostname + # check if doesn't already exist + if NIC.select_by(hostname=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')) - + + transaction = ctx.current.create_transaction() + try: + machine = testMachineId(user, fields.getfirst('machine_id')) + owner = testOwner(user, fields.getfirst('owner'), machine) + contact = testContact(user, fields.getfirst('contact')) + hostname = testHostname(owner, fields.getfirst('hostname'), + machine) + name = testName(user, fields.getfirst('name')) + oldname = machine.name + olddisk = {} + + memory = fields.getfirst('memory') + if memory is not None: + memory = validMemory(user, memory, machine) + if memory != machine.memory: + machine.memory = memory + + disksize = testDisk(user, fields.getfirst('disk')) + if disksize is not None: + disksize = validDisk(user, disksize, machine) + + for disk in machine.disks: + disk.size = disksize + olddisk[disk.guest_device_name] = disk.size + ctx.current.save(disk) + + # XXX all NICs get same hostname on change? Interface doesn't support more. + for nic in machine.nics: + nic.hostname = hostname + ctx.current.save(nic) + + if owner != machine.owner: + machine.owner = owner + if name != machine.name: + machine.name = name + + ctx.current.save(machine) + transaction.commit() + except: + transaction.rollback() + remctl("web", "moveregister", oldname, name) + for disk in machine.disks: + # XXX all disks get the same size on change? Interface doesn't support more. + if disk.size != olddisk[disk.guest_device_name]: + remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size)) + if oldname != name: + remctl("web", "lvrename", oldname, disk.guest_device_name, name) + d = dict(user=user, + command="modify", + machine=machine) + return Template(file="command.tmpl", searchList=[d, global_dict]) + + def help(user, fields): """Handler for help messages.""" simple = fields.getfirst('simple') @@ -569,7 +673,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): @@ -644,7 +748,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, @@ -672,28 +776,50 @@ if __name__ == '__main__': u.email = 'nobody' connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen') operation = os.environ.get('PATH_INFO', '') - #print 'Content-Type: text/plain\n' - #print operation +# print 'Content-Type: text/plain\n' +# print operation if not operation: 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 = str(output) + output = output.replace('', '

STDERR:

'+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() + sys.stderr=sys.stdout + 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() + sys.stderr=sys.stdout + 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 '----' + sys.stderr = sys.stdout + raise