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

Last change on this file since 170 was 167, checked in by ecprice, 17 years ago

don't display uptime, cputime when machine is off.

  • Property svn:executable set to *
File size: 28.5 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
[133]14import datetime
[153]15import StringIO
[161]16import getafsgroups
[113]17
[153]18sys.stderr = StringIO.StringIO()
[113]19sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
20
21from Cheetah.Template import Template
22from sipb_xen_database import *
23import random
24
[119]25class MyException(Exception):
[145]26    """Base class for my exceptions"""
[119]27    pass
28
[145]29class InvalidInput(MyException):
30    """Exception for user-provided input is invalid but maybe in good faith.
31
32    This would include setting memory to negative (which might be a
33    typo) but not setting an invalid boot CD (which requires bypassing
34    the select box).
35    """
[153]36    def __init__(self, err_field, err_value, expl=None):
[164]37        MyException.__init__(self, expl)
[153]38        self.err_field = err_field
39        self.err_value = err_value
[145]40
41class CodeError(MyException):
42    """Exception for internal errors or bad faith input."""
43    pass
44
[152]45class Global(object):
46    def __init__(self, user):
47        self.user = user
[145]48
[152]49    def __get_uptimes(self):
50        if not hasattr(self, '_uptimes'):
[157]51            self._uptimes = getUptimes(Machine.select())
[152]52        return self._uptimes
53    uptimes = property(__get_uptimes)
[145]54
[152]55g = None
56
[139]57def helppopup(subj):
[145]58    """Return HTML code for a (?) link to a specified help topic"""
[139]59    return '<span class="helplink"><a href="help?subject='+subj+'&amp;simple=true" target="_blank" onclick="return helppopup(\''+subj+'\')">(?)</a></span>'
60
61
62global_dict = {}
63global_dict['helppopup'] = helppopup
64
65
[113]66# ... and stolen from xend/uuid.py
67def randomUUID():
68    """Generate a random UUID."""
69
70    return [ random.randint(0, 255) for _ in range(0, 16) ]
71
72def uuidToString(u):
[145]73    """Turn a numeric UUID to a hyphen-seperated one."""
[113]74    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
75                     "%02x" * 6]) % tuple(u)
76
[144]77MAX_MEMORY_TOTAL = 512
78MAX_MEMORY_SINGLE = 256
79MIN_MEMORY_SINGLE = 16
80MAX_DISK_TOTAL = 50
81MAX_DISK_SINGLE = 50
82MIN_DISK_SINGLE = 0.1
83MAX_VMS_TOTAL = 10
84MAX_VMS_ACTIVE = 4
[113]85
[145]86def getMachinesByOwner(owner):
87    """Return the machines owned by a given owner."""
[144]88    return Machine.select_by(owner=owner)
89
[152]90def maxMemory(user, machine=None):
[145]91    """Return the maximum memory for a machine or a user.
92
93    If machine is None, return the memory available for a new
94    machine.  Else, return the maximum that machine can have.
95
96    on is a dictionary from machines to booleans, whether a machine is
97    on.  If None, it is recomputed. XXX make this global?
98    """
99
100    machines = getMachinesByOwner(user.username)
[152]101    active_machines = [x for x in machines if g.uptimes[x]]
[144]102    mem_usage = sum([x.memory for x in active_machines if x != machine])
103    return min(MAX_MEMORY_SINGLE, MAX_MEMORY_TOTAL-mem_usage)
104
[133]105def maxDisk(user, machine=None):
[145]106    machines = getMachinesByOwner(user.username)
[144]107    disk_usage = sum([sum([y.size for y in x.disks])
108                      for x in machines if x != machine])
109    return min(MAX_DISK_SINGLE, MAX_DISK_TOTAL-disk_usage/1024.)
[119]110
[152]111def canAddVm(user):
[145]112    machines = getMachinesByOwner(user.username)
[152]113    active_machines = [x for x in machines if g.uptimes[x]]
[144]114    return (len(machines) < MAX_VMS_TOTAL and
115            len(active_machines) < MAX_VMS_ACTIVE)
116
[113]117def haveAccess(user, machine):
[145]118    """Return whether a user has access to a machine"""
[138]119    if user.username == 'moo':
[135]120        return True
[161]121    return getafsgroups.checkLockerOwner(user.username,machine.owner)
[113]122
[153]123def error(op, user, fields, err, emsg):
[145]124    """Print an error page when a CodeError occurs"""
[153]125    d = dict(op=op, user=user, errorMessage=str(err),
126             stderr=emsg)
127    return Template(file='error.tmpl', searchList=[d, global_dict]);
[113]128
[153]129def invalidInput(op, user, fields, err, emsg):
130    """Print an error page when an InvalidInput exception occurs"""
131    d = dict(op=op, user=user, err_field=err.err_field,
132             err_value=str(err.err_value), stderr=emsg,
133             errorMessage=str(err))
134    return Template(file='invalid.tmpl', searchList=[d, global_dict]);
135
[113]136def validMachineName(name):
[119]137    """Check that name is valid for a machine name"""
[113]138    if not name:
139        return False
[119]140    charset = string.ascii_letters + string.digits + '-_'
141    if name[0] in '-_' or len(name) > 22:
[113]142        return False
[140]143    for x in name:
144        if x not in charset:
145            return False
146    return True
[113]147
[119]148def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
149    """Kinit with a given username and keytab"""
[113]150
[133]151    p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
152                         stderr=subprocess.PIPE)
[119]153    e = p.wait()
154    if e:
[145]155        raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
[119]156
[113]157def checkKinit():
[119]158    """If we lack tickets, kinit."""
[113]159    p = subprocess.Popen(['klist', '-s'])
160    if p.wait():
161        kinit()
162
[119]163def remctl(*args, **kws):
164    """Perform a remctl and return the output.
165
166    kinits if necessary, and outputs errors to stderr.
167    """
[113]168    checkKinit()
169    p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
170                         + list(args),
171                         stdout=subprocess.PIPE,
172                         stderr=subprocess.PIPE)
[119]173    if kws.get('err'):
[144]174        p.wait()
[119]175        return p.stdout.read(), p.stderr.read()
[113]176    if p.wait():
[145]177        raise CodeError('ERROR on remctl %s: %s' %
[144]178                          (args, p.stderr.read()))
[119]179    return p.stdout.read()
[113]180
[152]181def lvcreate(machine, disk):
182    """Create a single disk for a machine"""
183    remctl('web', 'lvcreate', machine.name,
184           disk.guest_device_name, str(disk.size))
185   
186def makeDisks(machine):
187    """Update the lvm partitions to add a disk."""
188    for disk in machine.disks:
189        lvcreate(machine, disk)
[113]190
191def bootMachine(machine, cdtype):
[119]192    """Boot a machine with a given boot CD.
193
194    If cdtype is None, give no boot cd.  Otherwise, it is the string
195    id of the CD (e.g. 'gutsy_i386')
196    """
[113]197    if cdtype is not None:
[119]198        remctl('web', 'vmboot', machine.name,
[113]199               cdtype)
200    else:
[119]201        remctl('web', 'vmboot', machine.name)
[113]202
[119]203def registerMachine(machine):
204    """Register a machine to be controlled by the web interface"""
205    remctl('web', 'register', machine.name)
206
[133]207def unregisterMachine(machine):
208    """Unregister a machine to not be controlled by the web interface"""
209    remctl('web', 'unregister', machine.name)
210
[119]211def parseStatus(s):
212    """Parse a status string into nested tuples of strings.
213
214    s = output of xm list --long <machine_name>
215    """
216    values = re.split('([()])', s)
217    stack = [[]]
218    for v in values[2:-2]: #remove initial and final '()'
219        if not v:
220            continue
221        v = v.strip()
222        if v == '(':
223            stack.append([])
224        elif v == ')':
[133]225            if len(stack[-1]) == 1:
226                stack[-1].append('')
[119]227            stack[-2].append(stack[-1])
228            stack.pop()
229        else:
230            if not v:
231                continue
232            stack[-1].extend(v.split())
233    return stack[-1]
234
[157]235def getUptimes(machines=None):
[133]236    """Return a dictionary mapping machine names to uptime strings"""
237    value_string = remctl('web', 'listvms')
238    lines = value_string.splitlines()
239    d = {}
[147]240    for line in lines:
[133]241        lst = line.split()
242        name, id = lst[:2]
243        uptime = ' '.join(lst[2:])
244        d[name] = uptime
[144]245    ans = {}
246    for m in machines:
247        ans[m] = d.get(m.name)
248    return ans
[133]249
[119]250def statusInfo(machine):
[133]251    """Return the status list for a given machine.
252
253    Gets and parses xm list --long
254    """
[119]255    value_string, err_string = remctl('list-long', machine.name, err=True)
256    if 'Unknown command' in err_string:
[145]257        raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
[119]258    elif 'does not exist' in err_string:
259        return None
260    elif err_string:
[145]261        raise CodeError("ERROR in remctl list-long %s%s" % (machine.name, err_string))
[119]262    status = parseStatus(value_string)
263    return status
264
265def hasVnc(status):
[133]266    """Does the machine with a given status list support VNC?"""
[119]267    if status is None:
268        return False
269    for l in status:
270        if l[0] == 'device' and l[1][0] == 'vfb':
271            d = dict(l[1][1:])
272            return 'location' in d
273    return False
274
[113]275def createVm(user, name, memory, disk, is_hvm, cdrom):
[133]276    """Create a VM and put it in the database"""
[113]277    # put stuff in the table
278    transaction = ctx.current.create_transaction()
279    try:
[144]280        if memory > maxMemory(user):
[153]281            raise InvalidInput('memory', memory,
282                               "Max %s" % maxMemory(user))
[144]283        if disk > maxDisk(user) * 1024:
[153]284            raise InvalidInput('disk', disk,
285                               "Max %s" % maxDisk(user))
[144]286        if not canAddVm(user):
[153]287            raise InvalidInput('create', True, 'Unable to create more VMs')
[113]288        res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
289        id = res.fetchone()[0]
290        machine = Machine()
291        machine.machine_id = id
292        machine.name = name
293        machine.memory = memory
294        machine.owner = user.username
295        machine.contact = user.email
296        machine.uuid = uuidToString(randomUUID())
297        machine.boot_off_cd = True
298        machine_type = Type.get_by(hvm=is_hvm)
299        machine.type_id = machine_type.type_id
300        ctx.current.save(machine)
301        disk = Disk(machine.machine_id, 
302                    'hda', disk)
303        open = NIC.select_by(machine_id=None)
304        if not open: #No IPs left!
[145]305            raise CodeError("No IP addresses left!  Contact sipb-xen-dev@mit.edu")
[113]306        nic = open[0]
307        nic.machine_id = machine.machine_id
308        nic.hostname = name
309        ctx.current.save(nic)   
310        ctx.current.save(disk)
311        transaction.commit()
312    except:
313        transaction.rollback()
314        raise
[144]315    registerMachine(machine)
[152]316    makeDisks(machine)
[113]317    # tell it to boot with cdrom
318    bootMachine(machine, cdrom)
319
320    return machine
321
[134]322def validMemory(user, memory, machine=None):
[145]323    """Parse and validate limits for memory for a given user and machine."""
[113]324    try:
325        memory = int(memory)
[144]326        if memory < MIN_MEMORY_SINGLE:
[113]327            raise ValueError
328    except ValueError:
[153]329        raise InvalidInput('memory', memory, 
330                           "Minimum %s MB" % MIN_MEMORY_SINGLE)
[134]331    if memory > maxMemory(user, machine):
[153]332        raise InvalidInput('memory', memory,
333                           'Maximum %s MB' % maxMemory(user, machine))
[134]334    return memory
335
336def validDisk(user, disk, machine=None):
[145]337    """Parse and validate limits for disk for a given user and machine."""
[113]338    try:
339        disk = float(disk)
[134]340        if disk > maxDisk(user, machine):
[153]341            raise InvalidInput('disk', disk,
342                               "Maximum %s G" % maxDisk(user, machine))
[113]343        disk = int(disk * 1024)
[144]344        if disk < MIN_DISK_SINGLE * 1024:
[113]345            raise ValueError
346    except ValueError:
[153]347        raise InvalidInput('disk', disk,
348                           "Minimum %s GB" % MIN_DISK_SINGLE)
[134]349    return disk
350
351def create(user, fields):
[145]352    """Handler for create requests."""
[134]353    name = fields.getfirst('name')
354    if not validMachineName(name):
[153]355        raise InvalidInput('name', name)
[162]356    name = name.lower()
[134]357
358    if Machine.get_by(name=name):
[153]359        raise InvalidInput('name', name,
360                           "Already exists")
[113]361   
[134]362    memory = fields.getfirst('memory')
363    memory = validMemory(user, memory)
364   
365    disk = fields.getfirst('disk')
366    disk = validDisk(user, disk)
367
[113]368    vm_type = fields.getfirst('vmtype')
369    if vm_type not in ('hvm', 'paravm'):
[145]370        raise CodeError("Invalid vm type '%s'"  % vm_type)   
[113]371    is_hvm = (vm_type == 'hvm')
372
373    cdrom = fields.getfirst('cdrom')
374    if cdrom is not None and not CDROM.get(cdrom):
[145]375        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
[113]376   
377    machine = createVm(user, name, memory, disk, is_hvm, cdrom)
378    d = dict(user=user,
379             machine=machine)
[153]380    return Template(file='create.tmpl',
[139]381                   searchList=[d, global_dict]);
[113]382
383def listVms(user, fields):
[145]384    """Handler for list requests."""
[135]385    machines = [m for m in Machine.select() if haveAccess(user, m)]   
[133]386    on = {}
[119]387    has_vnc = {}
[152]388    on = g.uptimes
[136]389    for m in machines:
[144]390        if not on[m]:
391            has_vnc[m] = 'Off'
[138]392        elif m.type.hvm:
[144]393            has_vnc[m] = True
[136]394        else:
[144]395            has_vnc[m] = "ParaVM"+helppopup("paravm_console")
[133]396    #     for m in machines:
397    #         status = statusInfo(m)
398    #         on[m.name] = status is not None
399    #         has_vnc[m.name] = hasVnc(status)
[152]400    max_mem=maxMemory(user)
[144]401    max_disk=maxDisk(user)
[113]402    d = dict(user=user,
[152]403             can_add_vm=canAddVm(user),
[144]404             max_mem=max_mem,
405             max_disk=max_disk,
406             default_mem=max_mem,
407             default_disk=min(4.0, max_disk),
[113]408             machines=machines,
[119]409             has_vnc=has_vnc,
[157]410             uptimes=g.uptimes,
[113]411             cdroms=CDROM.select())
[153]412    return Template(file='list.tmpl', searchList=[d, global_dict])
[113]413
414def testMachineId(user, machineId, exists=True):
[145]415    """Parse, validate and check authorization for a given machineId.
416
417    If exists is False, don't check that it exists.
418    """
[113]419    if machineId is None:
[145]420        raise CodeError("No machine ID specified")
[113]421    try:
422        machineId = int(machineId)
423    except ValueError:
[145]424        raise CodeError("Invalid machine ID '%s'" % machineId)
[113]425    machine = Machine.get(machineId)
426    if exists and machine is None:
[145]427        raise CodeError("No such machine ID '%s'" % machineId)
428    if machine is not None and not haveAccess(user, machine):
429        raise CodeError("No access to machine ID '%s'" % machineId)
[113]430    return machine
431
432def vnc(user, fields):
[119]433    """VNC applet page.
434
435    Note that due to same-domain restrictions, the applet connects to
436    the webserver, which needs to forward those requests to the xen
437    server.  The Xen server runs another proxy that (1) authenticates
438    and (2) finds the correct port for the VM.
439
440    You might want iptables like:
441
442    -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
443    -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
444    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
[145]445
446    Remember to enable iptables!
447    echo 1 > /proc/sys/net/ipv4/ip_forward
[119]448    """
[113]449    machine = testMachineId(user, fields.getfirst('machine_id'))
[118]450   
451    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
452
453    data = {}
[133]454    data["user"] = user.username
[120]455    data["machine"]=machine.name
[118]456    data["expires"]=time.time()+(5*60)
457    pickledData = cPickle.dumps(data)
458    m = hmac.new(TOKEN_KEY, digestmod=sha)
459    m.update(pickledData)
460    token = {'data': pickledData, 'digest': m.digest()}
461    token = cPickle.dumps(token)
462    token = base64.urlsafe_b64encode(token)
463   
[152]464    status = statusInfo(machine)
465    has_vnc = hasVnc(status)
466   
[113]467    d = dict(user=user,
[152]468             on=status,
469             has_vnc=has_vnc,
[113]470             machine=machine,
[119]471             hostname=os.environ.get('SERVER_NAME', 'localhost'),
[113]472             authtoken=token)
[153]473    return Template(file='vnc.tmpl',
[139]474                   searchList=[d, global_dict])
[113]475
[133]476def getNicInfo(data_dict, machine):
[145]477    """Helper function for info, get data on nics for a machine.
478
479    Modifies data_dict to include the relevant data, and returns a list
480    of (key, name) pairs to display "name: data_dict[key]" to the user.
481    """
[133]482    data_dict['num_nics'] = len(machine.nics)
483    nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
484                           ('nic%s_mac', 'NIC %s MAC Addr'),
485                           ('nic%s_ip', 'NIC %s IP'),
486                           ]
487    nic_fields = []
488    for i in range(len(machine.nics)):
489        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
490        data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
491        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
492        data_dict['nic%s_ip' % i] = machine.nics[i].ip
493    if len(machine.nics) == 1:
494        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
495    return nic_fields
496
497def getDiskInfo(data_dict, machine):
[145]498    """Helper function for info, get data on disks for a machine.
499
500    Modifies data_dict to include the relevant data, and returns a list
501    of (key, name) pairs to display "name: data_dict[key]" to the user.
502    """
[133]503    data_dict['num_disks'] = len(machine.disks)
504    disk_fields_template = [('%s_size', '%s size')]
505    disk_fields = []
506    for disk in machine.disks:
507        name = disk.guest_device_name
508        disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
509        data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
510    return disk_fields
511
512def deleteVM(machine):
[145]513    """Delete a VM."""
[159]514    try:
515        remctl('destroy', machine.name)
516    except:
517        pass
[133]518    transaction = ctx.current.create_transaction()
519    delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
520    try:
521        for nic in machine.nics:
522            nic.machine_id = None
523            nic.hostname = None
524            ctx.current.save(nic)
525        for disk in machine.disks:
526            ctx.current.delete(disk)
527        ctx.current.delete(machine)
528        transaction.commit()
529    except:
530        transaction.rollback()
531        raise
532    for mname, dname in delete_disk_pairs:
533        remctl('web', 'lvremove', mname, dname)
534    unregisterMachine(machine)
535
536def command(user, fields):
[145]537    """Handler for running commands like boot and delete on a VM."""
[157]538    print >> sys.stderr, time.time()-start_time
[133]539    machine = testMachineId(user, fields.getfirst('machine_id'))
540    action = fields.getfirst('action')
541    cdrom = fields.getfirst('cdrom')
[157]542    print >> sys.stderr, time.time()-start_time
[133]543    if cdrom is not None and not CDROM.get(cdrom):
[145]544        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
[133]545    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
[145]546        raise CodeError("Invalid action '%s'" % action)
[133]547    if action == 'Reboot':
548        if cdrom is not None:
549            remctl('reboot', machine.name, cdrom)
550        else:
551            remctl('reboot', machine.name)
552    elif action == 'Power on':
[144]553        if maxMemory(user) < machine.memory:
[153]554            raise InvalidInput('action', 'Power on',
555                               "You don't have enough free RAM quota to turn on this machine")
[133]556        bootMachine(machine, cdrom)
557    elif action == 'Power off':
558        remctl('destroy', machine.name)
559    elif action == 'Shutdown':
560        remctl('shutdown', machine.name)
561    elif action == 'Delete VM':
562        deleteVM(machine)
[157]563    print >> sys.stderr, time.time()-start_time
[133]564
565    d = dict(user=user,
566             command=action,
567             machine=machine)
[153]568    return Template(file="command.tmpl", searchList=[d, global_dict])
569
570def testOwner(user, owner, machine=None):
[161]571    if not getafsgroups.checkLockerOwner(user.username, owner):
[153]572        raise InvalidInput('owner', owner,
573                           "Invalid")
574    return owner
575
576def testContact(user, contact, machine=None):
577    if contact != user.email:
578        raise InvalidInput('contact', contact,
579                           "Invalid")
580    return contact
581
[161]582def testDisk(user, disksize, machine=None):
583    return disksize
584
585def testName(user, name, machine=None):
[163]586    if Machine.select_by(name=name) == []:
587        return name
[164]588    if name == machine.name:
589        return name
[163]590    raise InvalidInput('name', name,
591                       "Already taken")
[161]592
[153]593def testHostname(user, hostname, machine):
594    for nic in machine.nics:
595        if hostname == nic.hostname:
596            return hostname
[161]597    # check if doesn't already exist
598    if NIC.select_by(hostname=hostname) == []:
599        return hostname
[153]600    raise InvalidInput('hostname', hostname,
601                       "Different from before")
602
603
[133]604def modify(user, fields):
[145]605    """Handler for modifying attributes of a machine."""
606    #XXX not written yet
[161]607
608    transaction = ctx.current.create_transaction()
609    try:
610        machine = testMachineId(user, fields.getfirst('machine_id'))
611        owner = testOwner(user, fields.getfirst('owner'), machine)
612        contact = testContact(user, fields.getfirst('contact'))
613        hostname = testHostname(owner, fields.getfirst('hostname'),
[153]614                            machine)
[164]615        name = testName(user, fields.getfirst('name'), machine)
[161]616        oldname = machine.name
[165]617        command="modify"
[161]618        olddisk = {}
[153]619
[161]620        memory = fields.getfirst('memory')
621        if memory is not None:
622            memory = validMemory(user, memory, machine)
[164]623        else:
624            memory = machine.memory
[161]625        if memory != machine.memory:
626            machine.memory = memory
[153]627
[161]628        disksize = testDisk(user, fields.getfirst('disk'))
629        if disksize is not None:
630            disksize = validDisk(user, disksize, machine)
[165]631        else:
632            disksize = machine.disks[0].size
[161]633        for disk in machine.disks:
[165]634            olddisk[disk.guest_device_name] = disk.size
[161]635            disk.size = disksize
636            ctx.current.save(disk)
637       
638        # XXX all NICs get same hostname on change?  Interface doesn't support more.
639        for nic in machine.nics:
640            nic.hostname = hostname
641            ctx.current.save(nic)
642
643        if owner != machine.owner:
644            machine.owner = owner
645        if name != machine.name:
646            machine.name = name
647           
648        ctx.current.save(machine)
649        transaction.commit()
650    except:
651        transaction.rollback()
[163]652        raise
[161]653    remctl("web", "moveregister", oldname, name)
654    for disk in machine.disks:
655        # XXX all disks get the same size on change?  Interface doesn't support more.
656        if disk.size != olddisk[disk.guest_device_name]:
657            remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size))
658        if oldname != name:
659            remctl("web", "lvrename", oldname, disk.guest_device_name, name)
660    d = dict(user=user,
[165]661             command=command,
[161]662             machine=machine)
663    return Template(file="command.tmpl", searchList=[d, global_dict])   
664
665
[139]666def help(user, fields):
[145]667    """Handler for help messages."""
[139]668    simple = fields.getfirst('simple')
669    subjects = fields.getlist('subject')
670   
671    mapping = dict(paravm_console="""
672ParaVM machines do not support console access over VNC.  To access
673these machines, you either need to boot with a liveCD and ssh in or
674hope that the sipb-xen maintainers add support for serial consoles.""",
675                   hvm_paravm="""
676HVM machines use the virtualization features of the processor, while
677ParaVM machines use Xen's emulation of virtualization features.  You
678want an HVM virtualized machine.""",
[166]679                   cpu_weight="""Don't ask us!  We're as mystified as you are.""",
680                   owner="""The Owner must be the name of a locker that you are an AFS
681administrator of.  In particular, you or an AFS group you are a member
682of must have AFS rlidwka bits on the locker.  You can check see who
683administers the LOCKER locker using the command 'fs la /mit/LOCKER' on
684Athena.)""")
[139]685   
686    d = dict(user=user,
687             simple=simple,
688             subjects=subjects,
689             mapping=mapping)
690   
[153]691    return Template(file="help.tmpl", searchList=[d, global_dict])
[139]692   
[133]693
[113]694def info(user, fields):
[145]695    """Handler for info on a single VM."""
[113]696    machine = testMachineId(user, fields.getfirst('machine_id'))
[133]697    status = statusInfo(machine)
698    has_vnc = hasVnc(status)
699    if status is None:
700        main_status = dict(name=machine.name,
701                           memory=str(machine.memory))
[167]702        uptime=None
703        cputime=None
[133]704    else:
705        main_status = dict(status[1:])
[167]706        start_time = float(main_status.get('start_time', 0))
707        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
708        cpu_time_float = float(main_status.get('cpu_time', 0))
709        cputime = datetime.timedelta(seconds=int(cpu_time_float))
[133]710    display_fields = """name uptime memory state cpu_weight on_reboot
711     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
712    display_fields = [('name', 'Name'),
713                      ('owner', 'Owner'),
714                      ('contact', 'Contact'),
[136]715                      ('type', 'Type'),
[133]716                      'NIC_INFO',
717                      ('uptime', 'uptime'),
718                      ('cputime', 'CPU usage'),
719                      ('memory', 'RAM'),
720                      'DISK_INFO',
721                      ('state', 'state (xen format)'),
[139]722                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
[133]723                      ('on_reboot', 'Action on VM reboot'),
724                      ('on_poweroff', 'Action on VM poweroff'),
725                      ('on_crash', 'Action on VM crash'),
726                      ('on_xend_start', 'Action on Xen start'),
727                      ('on_xend_stop', 'Action on Xen stop'),
728                      ('bootloader', 'Bootloader options'),
729                      ]
730    fields = []
731    machine_info = {}
[147]732    machine_info['name'] = machine.name
[136]733    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]734    machine_info['owner'] = machine.owner
735    machine_info['contact'] = machine.contact
736
737    nic_fields = getNicInfo(machine_info, machine)
738    nic_point = display_fields.index('NIC_INFO')
739    display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
740
741    disk_fields = getDiskInfo(machine_info, machine)
742    disk_point = display_fields.index('DISK_INFO')
743    display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
744   
745    main_status['memory'] += ' MB'
746    for field, disp in display_fields:
[167]747        if field in ('uptime', 'cputime') and locals()[field] is not None:
[133]748            fields.append((disp, locals()[field]))
[147]749        elif field in machine_info:
750            fields.append((disp, machine_info[field]))
[133]751        elif field in main_status:
752            fields.append((disp, main_status[field]))
753        else:
754            pass
755            #fields.append((disp, None))
[144]756    max_mem = maxMemory(user, machine)
757    max_disk = maxDisk(user, machine)
[113]758    d = dict(user=user,
[133]759             cdroms=CDROM.select(),
760             on=status is not None,
761             machine=machine,
762             has_vnc=has_vnc,
763             uptime=str(uptime),
764             ram=machine.memory,
[144]765             max_mem=max_mem,
766             max_disk=max_disk,
[166]767             owner_help=helppopup("owner"),
[133]768             fields = fields)
[153]769    return Template(file='info.tmpl',
[139]770                   searchList=[d, global_dict])
[113]771
772mapping = dict(list=listVms,
773               vnc=vnc,
[133]774               command=command,
775               modify=modify,
[113]776               info=info,
[139]777               create=create,
778               help=help)
[113]779
780if __name__ == '__main__':
[133]781    start_time = time.time()
[113]782    fields = cgi.FieldStorage()
[133]783    class User:
[113]784        username = "moo"
785        email = 'moo@cow.com'
[133]786    u = User()
[152]787    g = Global(u)
[140]788    if 'SSL_CLIENT_S_DN_Email' in os.environ:
789        username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
790        u.username = username
791        u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
792    else:
[144]793        u.username = 'moo'
794        u.email = 'nobody'
[140]795    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
[113]796    operation = os.environ.get('PATH_INFO', '')
[157]797#    print 'Content-Type: text/plain\n'
798#    print operation
[119]799    if not operation:
[140]800        print "Status: 301 Moved Permanently"
801        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
802        sys.exit(0)
[119]803
[113]804    if operation.startswith('/'):
805        operation = operation[1:]
806    if not operation:
807        operation = 'list'
[157]808
[153]809    def badOperation(u, e):
810        raise CodeError("Unknown operation")
811
812    fun = mapping.get(operation, badOperation)
[139]813    if fun not in (help, ):
814        connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
[119]815    try:
[153]816        output = fun(u, fields)
817        print 'Content-Type: text/html\n'
818        sys.stderr.seek(0)
819        e = sys.stderr.read()
820        if e:
[157]821            output = str(output)
822            output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
[153]823        print output
[145]824    except CodeError, err:
[153]825        print 'Content-Type: text/html\n'
826        sys.stderr.seek(0)
827        e = sys.stderr.read()
[157]828        sys.stderr=sys.stdout
[153]829        print error(operation, u, fields, err, e)
[145]830    except InvalidInput, err:
[153]831        print 'Content-Type: text/html\n'
832        sys.stderr.seek(0)
833        e = sys.stderr.read()
[157]834        sys.stderr=sys.stdout
[153]835        print invalidInput(operation, u, fields, err, e)
836    except:
837        print 'Content-Type: text/plain\n'
838        sys.stderr.seek(0)
839        e = sys.stderr.read()
840        print e
841        print '----'
[157]842        sys.stderr = sys.stdout
[153]843        raise
Note: See TracBrowser for help on using the repository browser.