X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-web.git/blobdiff_plain/6a1f2bd00f0f8dbb52b25dd8a8e7c9cb8e2baccc..812770288a8001b9c4c3e42f24be7cbb754202d6:/code/main.py diff --git a/code/main.py b/code/main.py index 74365f5..f42e1fd 100755 --- a/code/main.py +++ b/code/main.py @@ -1,14 +1,18 @@ #!/usr/bin/python """Main CGI script for web interface""" +from __future__ import with_statement + import base64 import cPickle import cgi import datetime import hmac +import os import random import sha import sys +import threading import time import urllib import socket @@ -36,8 +40,18 @@ from invirt.config import structs as config from invirt.common import InvalidInput, CodeError from view import View, revertStandardError +import ajaxterm + + +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): @@ -53,6 +67,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): @@ -86,7 +102,7 @@ class InvirtWeb(View): def __getattr__(self, name): 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 +128,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 @@ -230,7 +244,7 @@ console will suffer artifacts. d['err'] = err if err: for field, value in fields.items(): - setattr(d['defaults'], field, value)) + setattr(d['defaults'], field, value) else: d['new_machine'] = parsed_fields['name'] return d @@ -248,11 +262,17 @@ 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: cherrypy.request.params['machine_id'] = int(name) return self @@ -267,7 +287,6 @@ console will suffer artifacts. 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 @@ -347,12 +366,12 @@ console will suffer artifacts. def command(self, command_name, machine_id, **kwargs): """Handler for running commands like boot and delete on a VM.""" 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' except InvalidInput, err: if not back: raise @@ -373,22 +392,50 @@ console will suffer artifacts. else: raise InvalidInput('back', back, 'Not a known back page.') - machine = MachineView() + atmulti = ajaxterm.Multiplex() + atsessions = {} + atsessions_lock = threading.Lock() -class Checkpoint: - def __init__(self): - self.start_time = time.time() - self.checkpoints = [] + @cherrypy.expose + @cherrypy.tools.mako(filename="/terminal.mako") + def terminal(self, machine_id): + machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine + + status = controls.statusInfo(machine) + has_vnc = hasVnc(status) + + d = dict(on=status, + has_vnc=has_vnc, + machine=machine, + hostname=cherrypy.request.local.name) + return d - def checkpoint(self, s): - self.checkpoints.append((s, time.time())) + @cherrypy.expose + @cherrypy.tools.gzip() + def at(self, machine_id, k=None, c=0, force=0): + machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine + with self.atsessions_lock: + if machine_id in self.atsessions: + term = self.atsessions[machine_id] + else: + print >>sys.stderr, "spawning new session for terminal to ",machine_id + term = self.atsessions[machine_id] = self.atmulti.create( + ["ssh", "-e","none", "-l", machine.name, config.console.hostname] + ) + if k: + self.atmulti.proc_write(term,k) + time.sleep(0.002) + dump=self.atmulti.dump(term,c,int(force)) + cherrypy.response.headers['Content-Type']='text/xml' + if isinstance(dump,str): + return dump + else: + print "Removing session for", machine_id + del self.atsessions[machine_id] + return '' - def __str__(self): - return ('Timing info:\n%s\n' % - '\n'.join(['%s: %s' % (d, t - self.start_time) for - (d, t) in self.checkpoints])) + machine = MachineView() -checkpoint = Checkpoint() class Defaults: """Class to store default values for fields.""" @@ -422,37 +469,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) @@ -582,7 +621,6 @@ def modifyDict(username, state, machine_id, fields): 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, @@ -596,7 +634,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'), @@ -642,11 +679,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(): @@ -654,7 +687,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, @@ -683,4 +715,4 @@ Subject: %s p.stdin.close() p.wait() -random.seed() +random.seed() #sigh