From 2d150399f4590fc1053fb667c2ce21d6b9e17833 Mon Sep 17 00:00:00 2001 From: Eric Price Date: Sun, 7 Oct 2007 17:32:53 -0400 Subject: [PATCH] More work. svn path=/trunk/web/; revision=119 --- templates/main.py | 192 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 133 insertions(+), 59 deletions(-) diff --git a/templates/main.py b/templates/main.py index 7a6ec8b..2c6430d 100755 --- a/templates/main.py +++ b/templates/main.py @@ -5,9 +5,12 @@ import cgi import os import string import subprocess +import re import time import cPickle import base64 +import sha +import hmac print 'Content-Type: text/html\n' sys.stderr = sys.stdout @@ -17,6 +20,9 @@ from Cheetah.Template import Template from sipb_xen_database import * import random +class MyException(Exception): + pass + # ... and stolen from xend/uuid.py def randomUUID(): """Generate a random UUID.""" @@ -27,60 +33,120 @@ def uuidToString(u): return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2, "%02x" * 6]) % tuple(u) - def maxMemory(user): return 256 +def maxDisk(user): + return 10.0 + def haveAccess(user, machine): return True - -def error(op, user, fields, errorMessage): - d = dict(op=op, - user=user, - errorMessage=errorMessage) - print Template(file='error.tmpl', - searchList=d); +def error(op, user, fields, err): + d = dict(op=op, user=user, errorMessage=str(err)) + print Template(file='error.tmpl', searchList=d); def validMachineName(name): + """Check that name is valid for a machine name""" if not name: return False - charset = string.ascii_letters + string.digits + '-' - if name[0] == '-' or len(name) > 22: + charset = string.ascii_letters + string.digits + '-_' + if name[0] in '-_' or len(name) > 22: return False return all(x in charset for x in name) -def kinit(): - keytab = '/etc/tabbott.keytab' - username = 'tabbott/extra' - p = subprocess.Popen(['kinit', "-k", "-t", keytab, - username]) - p.wait() +def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'): + """Kinit with a given username and keytab""" + + p = subprocess.Popen(['kinit', "-k", "-t", keytab, username]) + e = p.wait() + if e: + raise MyException("Error %s in kinit" % e) def checkKinit(): + """If we lack tickets, kinit.""" p = subprocess.Popen(['klist', '-s']) if p.wait(): kinit() -def remctl(*args): +def remctl(*args, **kws): + """Perform a remctl and return the output. + + kinits if necessary, and outputs errors to stderr. + """ checkKinit() p = subprocess.Popen(['remctl', 'black-mesa.mit.edu'] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if kws.get('err'): + return p.stdout.read(), p.stderr.read() if p.wait(): print >> sys.stderr, 'ERROR on remctl ', args print >> sys.stderr, p.stderr.read() + return p.stdout.read() def makeDisks(): - remctl('lvcreate','all') + """Update the lvm partitions to include all disks in the database.""" + remctl('web', 'lvcreate') def bootMachine(machine, cdtype): + """Boot a machine with a given boot CD. + + If cdtype is None, give no boot cd. Otherwise, it is the string + id of the CD (e.g. 'gutsy_i386') + """ if cdtype is not None: - remctl('vmboot', 'cdrom', str(machine.name), + remctl('web', 'vmboot', machine.name, cdtype) else: - remctl('vmboot', 'cdrom', str(machine.name)) + remctl('web', 'vmboot', machine.name) + +def registerMachine(machine): + """Register a machine to be controlled by the web interface""" + remctl('web', 'register', machine.name) + +def parseStatus(s): + """Parse a status string into nested tuples of strings. + + s = output of xm list --long + """ + values = re.split('([()])', s) + stack = [[]] + for v in values[2:-2]: #remove initial and final '()' + if not v: + continue + v = v.strip() + if v == '(': + stack.append([]) + elif v == ')': + stack[-2].append(stack[-1]) + stack.pop() + else: + if not v: + continue + stack[-1].extend(v.split()) + return stack[-1] + +def statusInfo(machine): + value_string, err_string = remctl('list-long', machine.name, err=True) + if 'Unknown command' in err_string: + raise MyException("ERROR in remctl list-long %s is not registered" % (machine.name,)) + elif 'does not exist' in err_string: + return None + elif err_string: + raise MyException("ERROR in remctl list-long %s: %s" % (machine.name, err_string)) + status = parseStatus(value_string) + return status + +def hasVnc(status): + if status is None: + return False + for l in status: + if l[0] == 'device' and l[1][0] == 'vfb': + d = dict(l[1][1:]) + return 'location' in d + return False def createVm(user, name, memory, disk, is_hvm, cdrom): # put stuff in the table @@ -114,6 +180,7 @@ def createVm(user, name, memory, disk, is_hvm, cdrom): transaction.rollback() raise makeDisks() + registerMachine(machine) # tell it to boot with cdrom bootMachine(machine, cdrom) @@ -122,13 +189,11 @@ def createVm(user, name, memory, disk, is_hvm, cdrom): def create(user, fields): name = fields.getfirst('name') if not validMachineName(name): - return error('create', user, fields, - "Invalid name '%s'" % name) + raise MyException("Invalid name '%s'" % name) name = name.lower() if Machine.get_by(name=name): - return error('create', user, fields, - "A machine named '%s' already exists" % name) + raise MyException("A machine named '%s' already exists" % name) memory = fields.getfirst('memory') try: @@ -136,11 +201,9 @@ def create(user, fields): if memory <= 0: raise ValueError except ValueError: - return error('create', user, fields, - "Invalid memory amount") + raise MyException("Invalid memory amount") if memory > maxMemory(user): - return error('create', user, fields, - "Too much memory requested") + raise MyException("Too much memory requested") disk = fields.getfirst('disk') try: @@ -149,24 +212,22 @@ def create(user, fields): if disk <= 0: raise ValueError except ValueError: - return error('create', user, fields, - "Invalid disk amount") + raise MyException("Invalid disk amount") + if disk > maxDisk(user): + raise MyException("Too much disk requested") vm_type = fields.getfirst('vmtype') if vm_type not in ('hvm', 'paravm'): - return error('create', user, fields, - "Invalid vm type '%s'" % vm_type) + raise MyException("Invalid vm type '%s'" % vm_type) is_hvm = (vm_type == 'hvm') cdrom = fields.getfirst('cdrom') if cdrom is not None and not CDROM.get(cdrom): - return error('create', user, fields, - "Invalid cdrom type '%s'" % cdrom) + raise MyException("Invalid cdrom type '%s'" % cdrom) machine = createVm(user, name, memory, disk, is_hvm, cdrom) if isinstance(machine, basestring): - return error('create', user, fields, - machine) + raise MyException(machine) d = dict(user=user, machine=machine) print Template(file='create.tmpl', @@ -174,41 +235,50 @@ def create(user, fields): def listVms(user, fields): machines = Machine.select() + status = statusInfo(machines) + has_vnc = {} + for m in machines: + on[m.name] = status[m.name] is not None + has_vnc[m.name] = hasVnc(status[m.name]) d = dict(user=user, + maxmem=maxMemory(user), + maxdisk=maxDisk(user), machines=machines, + status=status, + has_vnc=has_vnc, cdroms=CDROM.select()) - print Template(file='list.tmpl', searchList=d) def testMachineId(user, machineId, exists=True): if machineId is None: - error('vnc', user, fields, - "No machine ID specified") - return False + raise MyException("No machine ID specified") try: machineId = int(machineId) except ValueError: - error('vnc', user, fields, - "Invalid machine ID '%s'" - % machineId) - return False + raise MyException("Invalid machine ID '%s'" % machineId) machine = Machine.get(machineId) if exists and machine is None: - error('vnc', user, fields, - "No such machine ID '%s'" - % machineId) - return False + raise MyException("No such machine ID '%s'" % machineId) if not haveAccess(user, machine): - error('vnc', user, fields, - "No access to machine ID '%s'" - % machineId) - return False + raise MyException("No access to machine ID '%s'" % machineId) return machine def vnc(user, fields): + """VNC applet page. + + Note that due to same-domain restrictions, the applet connects to + the webserver, which needs to forward those requests to the xen + server. The Xen server runs another proxy that (1) authenticates + and (2) finds the correct port for the VM. + + You might want iptables like: + + -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp --dport 10003 -j DNAT --to-destination 18.181.0.60:10003 + -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp --dport 10003 -j SNAT --to-source 18.187.7.142 + -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT + """ machine = testMachineId(user, fields.getfirst('machine_id')) - if machine is None: #gave error page already - return + #XXX fix TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN" @@ -225,16 +295,13 @@ def vnc(user, fields): d = dict(user=user, machine=machine, - hostname='localhost', + hostname=os.environ.get('SERVER_NAME', 'localhost'), authtoken=token) print Template(file='vnc.tmpl', searchList=d) def info(user, fields): machine = testMachineId(user, fields.getfirst('machine_id')) - if machine is None: #gave error page already - return - d = dict(user=user, machine=machine) print Template(file='info.tmpl', @@ -253,6 +320,10 @@ if __name__ == '__main__': u = C() connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen') operation = os.environ.get('PATH_INFO', '') + if not operation: + pass + #XXX do redirect + if operation.startswith('/'): operation = operation[1:] if not operation: @@ -262,4 +333,7 @@ if __name__ == '__main__': lambda u, e: error(operation, u, e, "Invalid operation '%'" % operation)) - fun(u, fields) + try: + fun(u, fields) + except MyException, err: + error(operation, u, fields, err) -- 1.7.9.5