X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-web.git/blobdiff_plain/1adbd730e54e17845e3f41b03d774f1a1d9b8255..35568d9967fd7b5296c9caf93593ee34e82a05b0:/code/main.py diff --git a/code/main.py b/code/main.py index 786a087..f2ee6d1 100755 --- a/code/main.py +++ b/code/main.py @@ -6,6 +6,7 @@ import cPickle import cgi import datetime import hmac +import os import random import sha import sys @@ -37,11 +38,20 @@ from invirt.common import InvalidInput, CodeError from view import View, revertStandardError + +static_dir = os.path.join(os.path.dirname(__file__), 'static') +InvirtStatic = cherrypy.tools.staticdir.handler( + root=static_dir, + dir=static_dir, + section='/static') + class InvirtUnauthWeb(View): + static = InvirtStatic + @cherrypy.expose @cherrypy.tools.mako(filename="/unauth.mako") def index(self): - return {'simple': True} + return dict(simple=True) class InvirtWeb(View): def __init__(self): @@ -53,6 +63,8 @@ class InvirtWeb(View): 'from invirt import database'] self._cp_config['request.error_response'] = self.handle_error + static = InvirtStatic + @cherrypy.expose @cherrypy.tools.mako(filename="/invalid.mako") def invalidInput(self): @@ -85,8 +97,11 @@ class InvirtWeb(View): return d def __getattr__(self, name): + # At the point __getattr__ is called, tools haven't been run. Make sure the user is logged in. + cherrypy.tools.remote_user_login.callable() + if name in ("admin", "overlord"): - if not cherrypy.request.login in getAfsGroupMembers(config.adminacl, config.authz[0].cell): + if not cherrypy.request.login in getAfsGroupMembers(config.adminacl, config.authz.afs.cells[0].cell): raise InvalidInput('username', cherrypy.request.login, 'Not in admin group %s.' % config.adminacl) cherrypy.request.state = State(cherrypy.request.login, isadmin=True) @@ -112,11 +127,9 @@ class InvirtWeb(View): @cherrypy.tools.mako(filename="/list.mako") def list(self, result=None): """Handler for list requests.""" - checkpoint.checkpoint('Getting list dict') d = getListDict(cherrypy.request.login, cherrypy.request.state) if result is not None: d['result'] = result - checkpoint.checkpoint('Got list dict') return d index=list @@ -199,10 +212,16 @@ console will suffer artifacts. help._cp_config['tools.require_login.on'] = False def parseCreate(self, fields): - kws = dict([(kw, fields.get(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split() if fields.get(kw)]) - validate = validation.Validate(cherrypy.request.login, cherrypy.request.state, strict=True, **kws) - return dict(contact=cherrypy.request.login, name=validate.name, description=validate.description, memory=validate.memory, - disksize=validate.disksize, owner=validate.owner, machine_type=getattr(validate, 'vmtype', Defaults.type), + kws = dict([(kw, fields[kw]) for kw in + 'name description owner memory disksize vmtype cdrom autoinstall'.split() + if fields[kw]]) + validate = validation.Validate(cherrypy.request.login, + cherrypy.request.state, + strict=True, **kws) + return dict(contact=cherrypy.request.login, name=validate.name, + description=validate.description, memory=validate.memory, + disksize=validate.disksize, owner=validate.owner, + machine_type=getattr(validate, 'vmtype', Defaults.type), cdrom=getattr(validate, 'cdrom', None), autoinstall=getattr(validate, 'autoinstall', None)) @@ -213,7 +232,8 @@ console will suffer artifacts. """Handler for create requests.""" try: parsed_fields = self.parseCreate(fields) - machine = controls.createVm(cherrypy.request.login, cherrypy.request.state, **parsed_fields) + machine = controls.createVm(cherrypy.request.login, + cherrypy.request.state, **parsed_fields) except InvalidInput, err: pass else: @@ -222,8 +242,8 @@ console will suffer artifacts. d = getListDict(cherrypy.request.login, cherrypy.request.state) d['err'] = err if err: - for field in fields.keys(): - setattr(d['defaults'], field, fields.get(field)) + for field, value in fields.items(): + setattr(d['defaults'], field, value) else: d['new_machine'] = parsed_fields['name'] return d @@ -241,14 +261,19 @@ console will suffer artifacts. raise RuntimeError("test of the emergency broadcast system") class MachineView(View): - # This is hairy. Fix when CherryPy 3.2 is out. (rename to - # _cp_dispatch, and parse the argument as a list instead of - # string - def __getattr__(self, name): + """Synthesize attributes to allow RESTful URLs like + /machine/13/info. This is hairy. CherryPy 3.2 adds a + method called _cp_dispatch that allows you to explicitly + handle URLs that can't be mapped, and it allows you to + rewrite the path components and continue processing. + + This function gets the next path component being resolved + as a string. _cp_dispatch will get an array of strings + representing any subsequent path components as well.""" + try: - machine_id = int(name) - cherrypy.request.params['machine_id'] = machine_id + cherrypy.request.params['machine_id'] = int(name) return self except ValueError: return None @@ -257,9 +282,10 @@ console will suffer artifacts. @cherrypy.tools.mako(filename="/info.mako") def info(self, machine_id): """Handler for info on a single VM.""" - machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine + machine = validation.Validate(cherrypy.request.login, + cherrypy.request.state, + machine_id=machine_id).machine d = infoDict(cherrypy.request.login, cherrypy.request.state, machine) - checkpoint.checkpoint('Got infodict') return d index = info @@ -269,19 +295,24 @@ console will suffer artifacts. def modify(self, machine_id, **fields): """Handler for modifying attributes of a machine.""" try: - modify_dict = modifyDict(cherrypy.request.login, cherrypy.request.state, machine_id, fields) + modify_dict = modifyDict(cherrypy.request.login, + cherrypy.request.state, + machine_id, fields) except InvalidInput, err: result = None - machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine + machine = validation.Validate(cherrypy.request.login, + cherrypy.request.state, + machine_id=machine_id).machine else: machine = modify_dict['machine'] result = 'Success!' err = None - info_dict = infoDict(cherrypy.request.login, cherrypy.request.state, machine) + info_dict = infoDict(cherrypy.request.login, + cherrypy.request.state, machine) info_dict['err'] = err if err: - for field in fields.keys(): - setattr(info_dict['defaults'], field, fields.get(field)) + for field, value in fields.items(): + setattr(info_dict['defaults'], field, value) info_dict['result'] = result return info_dict @@ -307,8 +338,9 @@ console will suffer artifacts. Remember to enable iptables! echo 1 > /proc/sys/net/ipv4/ip_forward """ - machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine - + machine = validation.Validate(cherrypy.request.login, + cherrypy.request.state, + machine_id=machine_id).machine token = controls.vnctoken(machine) host = controls.listHost(machine) if host: @@ -326,16 +358,19 @@ console will suffer artifacts. port=port, authtoken=token) return d + @cherrypy.expose @cherrypy.tools.mako(filename="/command.mako") @cherrypy.tools.require_POST() def command(self, command_name, machine_id, **kwargs): """Handler for running commands like boot and delete on a VM.""" - back = kwargs.get('back', None) + back = kwargs.get('back') + if command_name == 'delete': + back = 'list' try: - d = controls.commandResult(cherrypy.request.login, cherrypy.request.state, command_name, machine_id, kwargs) - if d['command'] == 'Delete VM': - back = 'list' + d = controls.commandResult(cherrypy.request.login, + cherrypy.request.state, + command_name, machine_id, kwargs) except InvalidInput, err: if not back: raise @@ -347,28 +382,17 @@ console will suffer artifacts. return d if back == 'list': cherrypy.request.state.clear() #Changed global state - raise cherrypy.InternalRedirect('/list?result=%s' % urllib.quote(result)) + raise cherrypy.InternalRedirect('/list?result=%s' + % urllib.quote(result)) elif back == 'info': - raise cherrypy.HTTPRedirect(cherrypy.request.base + '/machine/%d/' % machine_id, status=303) + raise cherrypy.HTTPRedirect(cherrypy.request.base + + '/machine/%d/' % machine_id, + status=303) else: raise InvalidInput('back', back, 'Not a known back page.') machine = MachineView() -class Checkpoint: - def __init__(self): - self.start_time = time.time() - self.checkpoints = [] - - def checkpoint(self, s): - self.checkpoints.append((s, time.time())) - - def __str__(self): - return ('Timing info:\n%s\n' % - '\n'.join(['%s: %s' % (d, t - self.start_time) for - (d, t) in self.checkpoints])) - -checkpoint = Checkpoint() class Defaults: """Class to store default values for fields.""" @@ -402,37 +426,29 @@ def hasVnc(status): def getListDict(username, state): """Gets the list of local variables used by list.tmpl.""" - checkpoint.checkpoint('Starting') machines = state.machines - checkpoint.checkpoint('Got my machines') on = {} has_vnc = {} installing = {} xmlist = state.xmlist - checkpoint.checkpoint('Got uptimes') for m in machines: if m not in xmlist: has_vnc[m] = 'Off' m.uptime = None else: m.uptime = xmlist[m]['uptime'] + installing[m] = bool(xmlist[m].get('autoinstall')) if xmlist[m]['console']: has_vnc[m] = True elif m.type.hvm: has_vnc[m] = "WTF?" else: has_vnc[m] = "ParaVM" - if xmlist[m].get('autoinstall'): - installing[m] = True - else: - installing[m] = False max_memory = validation.maxMemory(username, state) max_disk = validation.maxDisk(username) - checkpoint.checkpoint('Got max mem/disk') defaults = Defaults(max_memory=max_memory, max_disk=max_disk, owner=username) - checkpoint.checkpoint('Got defaults') def sortkey(machine): return (machine.owner != username, machine.owner, machine.name) machines = sorted(machines, key=sortkey) @@ -507,7 +523,9 @@ def modifyDict(username, state, machine_id, fields): olddisk = {} session.begin() try: - kws = dict([(kw, fields.get(kw)) for kw in 'owner admin contact name description memory vmtype disksize'.split() if fields.get(kw)]) + kws = dict((kw, fields[kw]) for kw in + 'owner admin contact name description memory vmtype disksize'.split() + if fields.get(kw)) kws['machine_id'] = machine_id validate = validation.Validate(username, state, **kws) machine = validate.machine @@ -519,6 +537,26 @@ def modifyDict(username, state, machine_id, fields): if hasattr(validate, 'vmtype'): machine.type = validate.vmtype + update_acl = False + if hasattr(validate, 'owner') and validate.owner != machine.owner: + machine.owner = validate.owner + update_acl = True + if hasattr(validate, 'description'): + machine.description = validate.description + if hasattr(validate, 'admin') and validate.admin != machine.administrator: + machine.administrator = validate.admin + update_acl = True + if hasattr(validate, 'contact'): + machine.contact = validate.contact + + session.save_or_update(machine) + session.commit() + except: + session.rollback() + raise + + session.begin() + try: if hasattr(validate, 'disksize'): disksize = validate.disksize disk = machine.disks[0] @@ -526,41 +564,37 @@ def modifyDict(username, state, machine_id, fields): olddisk[disk.guest_device_name] = disksize disk.size = disksize session.save_or_update(disk) + for diskname in olddisk: + controls.resizeDisk(oldname, diskname, str(olddisk[diskname])) + session.save_or_update(machine) + session.commit() + except: + session.rollback() + raise - update_acl = False - if hasattr(validate, 'owner') and validate.owner != machine.owner: - machine.owner = validate.owner - update_acl = True + session.begin() + try: if hasattr(validate, 'name'): machine.name = validate.name for n in machine.nics: if n.hostname == oldname: n.hostname = validate.name - if hasattr(validate, 'description'): - machine.description = validate.description - if hasattr(validate, 'admin') and validate.admin != machine.administrator: - machine.administrator = validate.admin - update_acl = True - if hasattr(validate, 'contact'): - machine.contact = validate.contact - + if hasattr(validate, 'name'): + controls.renameMachine(machine, oldname, validate.name) session.save_or_update(machine) - if update_acl: - cache_acls.refreshMachine(machine) session.commit() except: session.rollback() raise - for diskname in olddisk: - controls.resizeDisk(oldname, diskname, str(olddisk[diskname])) - if hasattr(validate, 'name'): - controls.renameMachine(machine, oldname, validate.name) + + if update_acl: + cache_acls.refreshMachine(machine) + return dict(machine=machine) def infoDict(username, state, machine): """Get the variables used by info.tmpl.""" status = controls.statusInfo(machine) - checkpoint.checkpoint('Getting status info') has_vnc = hasVnc(status) if status is None: main_status = dict(name=machine.name, @@ -574,7 +608,6 @@ def infoDict(username, state, machine): uptime = datetime.timedelta(seconds=int(time.time()-start_time)) cpu_time_float = float(main_status.get('cpu_time', 0)) cputime = datetime.timedelta(seconds=int(cpu_time_float)) - checkpoint.checkpoint('Status') display_fields = [('name', 'Name'), ('description', 'Description'), ('owner', 'Owner'), @@ -620,11 +653,7 @@ def infoDict(username, state, machine): pass #fields.append((disp, None)) - checkpoint.checkpoint('Got fields') - - max_mem = validation.maxMemory(machine.owner, state, machine, False) - checkpoint.checkpoint('Got mem') max_disk = validation.maxDisk(machine.owner, machine) defaults = Defaults() for name in 'machine_id name description administrator owner memory contact'.split(): @@ -632,7 +661,6 @@ def infoDict(username, state, machine): setattr(defaults, name, getattr(machine, name)) defaults.type = machine.type.type_id defaults.disk = "%0.2f" % (machine.disks[0].size/1024.) - checkpoint.checkpoint('Got defaults') d = dict(user=username, on=status is not None, machine=machine, @@ -661,4 +689,4 @@ Subject: %s p.stdin.close() p.wait() -random.seed() +random.seed() #sigh