source: trunk/web/templates/main.py @ 129

Last change on this file since 129 was 120, checked in by quentin, 17 years ago

Don't store unnecessary info in the token

  • Property svn:executable set to *
File size: 10.1 KB
RevLine 
[113]1#!/usr/bin/python
2
3import sys
4import cgi
5import os
6import string
7import subprocess
[119]8import re
[118]9import time
10import cPickle
11import base64
[119]12import sha
13import hmac
[113]14
15print 'Content-Type: text/html\n'
16sys.stderr = sys.stdout
17sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
18
19from Cheetah.Template import Template
20from sipb_xen_database import *
21import random
22
[119]23class MyException(Exception):
24    pass
25
[113]26# ... and stolen from xend/uuid.py
27def randomUUID():
28    """Generate a random UUID."""
29
30    return [ random.randint(0, 255) for _ in range(0, 16) ]
31
32def uuidToString(u):
33    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
34                     "%02x" * 6]) % tuple(u)
35
36def maxMemory(user):
37    return 256
38
[119]39def maxDisk(user):
40    return 10.0
41
[113]42def haveAccess(user, machine):
43    return True
44
[119]45def error(op, user, fields, err):
46    d = dict(op=op, user=user, errorMessage=str(err))
47    print Template(file='error.tmpl', searchList=d);
[113]48
49def validMachineName(name):
[119]50    """Check that name is valid for a machine name"""
[113]51    if not name:
52        return False
[119]53    charset = string.ascii_letters + string.digits + '-_'
54    if name[0] in '-_' or len(name) > 22:
[113]55        return False
56    return all(x in charset for x in name)
57
[119]58def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
59    """Kinit with a given username and keytab"""
[113]60
[119]61    p = subprocess.Popen(['kinit', "-k", "-t", keytab, username])
62    e = p.wait()
63    if e:
64        raise MyException("Error %s in kinit" % e)
65
[113]66def checkKinit():
[119]67    """If we lack tickets, kinit."""
[113]68    p = subprocess.Popen(['klist', '-s'])
69    if p.wait():
70        kinit()
71
[119]72def remctl(*args, **kws):
73    """Perform a remctl and return the output.
74
75    kinits if necessary, and outputs errors to stderr.
76    """
[113]77    checkKinit()
78    p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
79                         + list(args),
80                         stdout=subprocess.PIPE,
81                         stderr=subprocess.PIPE)
[119]82    if kws.get('err'):
83        return p.stdout.read(), p.stderr.read()
[113]84    if p.wait():
85        print >> sys.stderr, 'ERROR on remctl ', args
86        print >> sys.stderr, p.stderr.read()
[119]87    return p.stdout.read()
[113]88
89def makeDisks():
[119]90    """Update the lvm partitions to include all disks in the database."""
91    remctl('web', 'lvcreate')
[113]92
93def bootMachine(machine, cdtype):
[119]94    """Boot a machine with a given boot CD.
95
96    If cdtype is None, give no boot cd.  Otherwise, it is the string
97    id of the CD (e.g. 'gutsy_i386')
98    """
[113]99    if cdtype is not None:
[119]100        remctl('web', 'vmboot', machine.name,
[113]101               cdtype)
102    else:
[119]103        remctl('web', 'vmboot', machine.name)
[113]104
[119]105def registerMachine(machine):
106    """Register a machine to be controlled by the web interface"""
107    remctl('web', 'register', machine.name)
108
109def parseStatus(s):
110    """Parse a status string into nested tuples of strings.
111
112    s = output of xm list --long <machine_name>
113    """
114    values = re.split('([()])', s)
115    stack = [[]]
116    for v in values[2:-2]: #remove initial and final '()'
117        if not v:
118            continue
119        v = v.strip()
120        if v == '(':
121            stack.append([])
122        elif v == ')':
123            stack[-2].append(stack[-1])
124            stack.pop()
125        else:
126            if not v:
127                continue
128            stack[-1].extend(v.split())
129    return stack[-1]
130
131def statusInfo(machine):
132    value_string, err_string = remctl('list-long', machine.name, err=True)
133    if 'Unknown command' in err_string:
134        raise MyException("ERROR in remctl list-long %s is not registered" % (machine.name,))
135    elif 'does not exist' in err_string:
136        return None
137    elif err_string:
138        raise MyException("ERROR in remctl list-long %s%s" % (machine.name, err_string))
139    status = parseStatus(value_string)
140    return status
141
142def hasVnc(status):
143    if status is None:
144        return False
145    for l in status:
146        if l[0] == 'device' and l[1][0] == 'vfb':
147            d = dict(l[1][1:])
148            return 'location' in d
149    return False
150
[113]151def createVm(user, name, memory, disk, is_hvm, cdrom):
152    # put stuff in the table
153    transaction = ctx.current.create_transaction()
154    try:
155        res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
156        id = res.fetchone()[0]
157        machine = Machine()
158        machine.machine_id = id
159        machine.name = name
160        machine.memory = memory
161        machine.owner = user.username
162        machine.contact = user.email
163        machine.uuid = uuidToString(randomUUID())
164        machine.boot_off_cd = True
165        machine_type = Type.get_by(hvm=is_hvm)
166        machine.type_id = machine_type.type_id
167        ctx.current.save(machine)
168        disk = Disk(machine.machine_id, 
169                    'hda', disk)
170        open = NIC.select_by(machine_id=None)
171        if not open: #No IPs left!
172            return "No IP addresses left!  Contact sipb-xen-dev@mit.edu"
173        nic = open[0]
174        nic.machine_id = machine.machine_id
175        nic.hostname = name
176        ctx.current.save(nic)   
177        ctx.current.save(disk)
178        transaction.commit()
179    except:
180        transaction.rollback()
181        raise
182    makeDisks()
[119]183    registerMachine(machine)
[113]184    # tell it to boot with cdrom
185    bootMachine(machine, cdrom)
186
187    return machine
188
189def create(user, fields):
190    name = fields.getfirst('name')
191    if not validMachineName(name):
[119]192        raise MyException("Invalid name '%s'" % name)
[113]193    name = name.lower()
194
195    if Machine.get_by(name=name):
[119]196        raise MyException("A machine named '%s' already exists" % name)
[113]197   
198    memory = fields.getfirst('memory')
199    try:
200        memory = int(memory)
201        if memory <= 0:
202            raise ValueError
203    except ValueError:
[119]204        raise MyException("Invalid memory amount")
[113]205    if memory > maxMemory(user):
[119]206        raise MyException("Too much memory requested")
[113]207   
208    disk = fields.getfirst('disk')
209    try:
210        disk = float(disk)
211        disk = int(disk * 1024)
212        if disk <= 0:
213            raise ValueError
214    except ValueError:
[119]215        raise MyException("Invalid disk amount")
216    if disk > maxDisk(user):
217        raise MyException("Too much disk requested")
[113]218   
219    vm_type = fields.getfirst('vmtype')
220    if vm_type not in ('hvm', 'paravm'):
[119]221        raise MyException("Invalid vm type '%s'"  % vm_type)   
[113]222    is_hvm = (vm_type == 'hvm')
223
224    cdrom = fields.getfirst('cdrom')
225    if cdrom is not None and not CDROM.get(cdrom):
[119]226        raise MyException("Invalid cdrom type '%s'" % cdrom)   
[113]227   
228    machine = createVm(user, name, memory, disk, is_hvm, cdrom)
229    if isinstance(machine, basestring):
[119]230        raise MyException(machine)
[113]231    d = dict(user=user,
232             machine=machine)
233    print Template(file='create.tmpl',
234                   searchList=d);
235
236def listVms(user, fields):
237    machines = Machine.select()
[119]238    status = statusInfo(machines)
239    has_vnc = {}
240    for m in machines:
241        on[m.name] = status[m.name] is not None
242        has_vnc[m.name] = hasVnc(status[m.name])
[113]243    d = dict(user=user,
[119]244             maxmem=maxMemory(user),
245             maxdisk=maxDisk(user),
[113]246             machines=machines,
[119]247             status=status,
248             has_vnc=has_vnc,
[113]249             cdroms=CDROM.select())
250    print Template(file='list.tmpl', searchList=d)
251
252def testMachineId(user, machineId, exists=True):
253    if machineId is None:
[119]254        raise MyException("No machine ID specified")
[113]255    try:
256        machineId = int(machineId)
257    except ValueError:
[119]258        raise MyException("Invalid machine ID '%s'" % machineId)
[113]259    machine = Machine.get(machineId)
260    if exists and machine is None:
[119]261        raise MyException("No such machine ID '%s'" % machineId)
[113]262    if not haveAccess(user, machine):
[119]263        raise MyException("No access to machine ID '%s'" % machineId)
[113]264    return machine
265
266def vnc(user, fields):
[119]267    """VNC applet page.
268
269    Note that due to same-domain restrictions, the applet connects to
270    the webserver, which needs to forward those requests to the xen
271    server.  The Xen server runs another proxy that (1) authenticates
272    and (2) finds the correct port for the VM.
273
274    You might want iptables like:
275
276    -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
277    -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
278    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
279    """
[113]280    machine = testMachineId(user, fields.getfirst('machine_id'))
[119]281    #XXX fix
[118]282   
283    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
284
285    data = {}
286    data["user"] = user
[120]287    data["machine"]=machine.name
[118]288    data["expires"]=time.time()+(5*60)
289    pickledData = cPickle.dumps(data)
290    m = hmac.new(TOKEN_KEY, digestmod=sha)
291    m.update(pickledData)
292    token = {'data': pickledData, 'digest': m.digest()}
293    token = cPickle.dumps(token)
294    token = base64.urlsafe_b64encode(token)
295   
[113]296    d = dict(user=user,
297             machine=machine,
[119]298             hostname=os.environ.get('SERVER_NAME', 'localhost'),
[113]299             authtoken=token)
300    print Template(file='vnc.tmpl',
301                   searchList=d)
302
303def info(user, fields):
304    machine = testMachineId(user, fields.getfirst('machine_id'))
305    d = dict(user=user,
306             machine=machine)
307    print Template(file='info.tmpl',
308                   searchList=d)
309
310mapping = dict(list=listVms,
311               vnc=vnc,
312               info=info,
313               create=create)
314
315if __name__ == '__main__':
316    fields = cgi.FieldStorage()
317    class C:
318        username = "moo"
319        email = 'moo@cow.com'
320    u = C()
321    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
322    operation = os.environ.get('PATH_INFO', '')
[119]323    if not operation:
324        pass
325        #XXX do redirect
326
[113]327    if operation.startswith('/'):
328        operation = operation[1:]
329    if not operation:
330        operation = 'list'
331   
332    fun = mapping.get(operation, 
333                      lambda u, e:
334                          error(operation, u, e,
335                                "Invalid operation '%'" % operation))
[119]336    try:
337        fun(u, fields)
338    except MyException, err:
339        error(operation, u, fields, err)
Note: See TracBrowser for help on using the repository browser.