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

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

fix invalid input exception, and let you "change" machine to it's own name

  • Property svn:executable set to *
File size: 28.0 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
617        olddisk = {}
[153]618
[161]619        memory = fields.getfirst('memory')
620        if memory is not None:
621            memory = validMemory(user, memory, machine)
[164]622        else:
623            memory = machine.memory
[161]624        if memory != machine.memory:
625            machine.memory = memory
[153]626
[161]627        disksize = testDisk(user, fields.getfirst('disk'))
628        if disksize is not None:
629            disksize = validDisk(user, disksize, machine)
630        for disk in machine.disks:
631            disk.size = disksize
632            olddisk[disk.guest_device_name] = disk.size
633            ctx.current.save(disk)
634       
635        # XXX all NICs get same hostname on change?  Interface doesn't support more.
636        for nic in machine.nics:
637            nic.hostname = hostname
638            ctx.current.save(nic)
639
640        if owner != machine.owner:
641            machine.owner = owner
642        if name != machine.name:
643            machine.name = name
644           
645        ctx.current.save(machine)
646        transaction.commit()
647    except:
648        transaction.rollback()
[163]649        raise
[161]650    remctl("web", "moveregister", oldname, name)
651    for disk in machine.disks:
652        # XXX all disks get the same size on change?  Interface doesn't support more.
653        if disk.size != olddisk[disk.guest_device_name]:
654            remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size))
655        if oldname != name:
656            remctl("web", "lvrename", oldname, disk.guest_device_name, name)
657    d = dict(user=user,
658             command="modify",
659             machine=machine)
660    return Template(file="command.tmpl", searchList=[d, global_dict])   
661
662
[139]663def help(user, fields):
[145]664    """Handler for help messages."""
[139]665    simple = fields.getfirst('simple')
666    subjects = fields.getlist('subject')
667   
668    mapping = dict(paravm_console="""
669ParaVM machines do not support console access over VNC.  To access
670these machines, you either need to boot with a liveCD and ssh in or
671hope that the sipb-xen maintainers add support for serial consoles.""",
672                   hvm_paravm="""
673HVM machines use the virtualization features of the processor, while
674ParaVM machines use Xen's emulation of virtualization features.  You
675want an HVM virtualized machine.""",
676                   cpu_weight="""Don't ask us!  We're as mystified as you are.""")
677   
678    d = dict(user=user,
679             simple=simple,
680             subjects=subjects,
681             mapping=mapping)
682   
[153]683    return Template(file="help.tmpl", searchList=[d, global_dict])
[139]684   
[133]685
[113]686def info(user, fields):
[145]687    """Handler for info on a single VM."""
[113]688    machine = testMachineId(user, fields.getfirst('machine_id'))
[133]689    status = statusInfo(machine)
690    has_vnc = hasVnc(status)
691    if status is None:
692        main_status = dict(name=machine.name,
693                           memory=str(machine.memory))
694    else:
695        main_status = dict(status[1:])
696    start_time = float(main_status.get('start_time', 0))
697    uptime = datetime.timedelta(seconds=int(time.time()-start_time))
698    cpu_time_float = float(main_status.get('cpu_time', 0))
699    cputime = datetime.timedelta(seconds=int(cpu_time_float))
700    display_fields = """name uptime memory state cpu_weight on_reboot
701     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
702    display_fields = [('name', 'Name'),
703                      ('owner', 'Owner'),
704                      ('contact', 'Contact'),
[136]705                      ('type', 'Type'),
[133]706                      'NIC_INFO',
707                      ('uptime', 'uptime'),
708                      ('cputime', 'CPU usage'),
709                      ('memory', 'RAM'),
710                      'DISK_INFO',
711                      ('state', 'state (xen format)'),
[139]712                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
[133]713                      ('on_reboot', 'Action on VM reboot'),
714                      ('on_poweroff', 'Action on VM poweroff'),
715                      ('on_crash', 'Action on VM crash'),
716                      ('on_xend_start', 'Action on Xen start'),
717                      ('on_xend_stop', 'Action on Xen stop'),
718                      ('bootloader', 'Bootloader options'),
719                      ]
720    fields = []
721    machine_info = {}
[147]722    machine_info['name'] = machine.name
[136]723    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
[133]724    machine_info['owner'] = machine.owner
725    machine_info['contact'] = machine.contact
726
727    nic_fields = getNicInfo(machine_info, machine)
728    nic_point = display_fields.index('NIC_INFO')
729    display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
730
731    disk_fields = getDiskInfo(machine_info, machine)
732    disk_point = display_fields.index('DISK_INFO')
733    display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
734   
735    main_status['memory'] += ' MB'
736    for field, disp in display_fields:
737        if field in ('uptime', 'cputime'):
738            fields.append((disp, locals()[field]))
[147]739        elif field in machine_info:
740            fields.append((disp, machine_info[field]))
[133]741        elif field in main_status:
742            fields.append((disp, main_status[field]))
743        else:
744            pass
745            #fields.append((disp, None))
[144]746    max_mem = maxMemory(user, machine)
747    max_disk = maxDisk(user, machine)
[113]748    d = dict(user=user,
[133]749             cdroms=CDROM.select(),
750             on=status is not None,
751             machine=machine,
752             has_vnc=has_vnc,
753             uptime=str(uptime),
754             ram=machine.memory,
[144]755             max_mem=max_mem,
756             max_disk=max_disk,
[133]757             fields = fields)
[153]758    return Template(file='info.tmpl',
[139]759                   searchList=[d, global_dict])
[113]760
761mapping = dict(list=listVms,
762               vnc=vnc,
[133]763               command=command,
764               modify=modify,
[113]765               info=info,
[139]766               create=create,
767               help=help)
[113]768
769if __name__ == '__main__':
[133]770    start_time = time.time()
[113]771    fields = cgi.FieldStorage()
[133]772    class User:
[113]773        username = "moo"
774        email = 'moo@cow.com'
[133]775    u = User()
[152]776    g = Global(u)
[140]777    if 'SSL_CLIENT_S_DN_Email' in os.environ:
778        username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
779        u.username = username
780        u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
781    else:
[144]782        u.username = 'moo'
783        u.email = 'nobody'
[140]784    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
[113]785    operation = os.environ.get('PATH_INFO', '')
[157]786#    print 'Content-Type: text/plain\n'
787#    print operation
[119]788    if not operation:
[140]789        print "Status: 301 Moved Permanently"
790        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
791        sys.exit(0)
[119]792
[113]793    if operation.startswith('/'):
794        operation = operation[1:]
795    if not operation:
796        operation = 'list'
[157]797
[153]798    def badOperation(u, e):
799        raise CodeError("Unknown operation")
800
801    fun = mapping.get(operation, badOperation)
[139]802    if fun not in (help, ):
803        connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
[119]804    try:
[153]805        output = fun(u, fields)
806        print 'Content-Type: text/html\n'
807        sys.stderr.seek(0)
808        e = sys.stderr.read()
809        if e:
[157]810            output = str(output)
811            output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
[153]812        print output
[145]813    except CodeError, err:
[153]814        print 'Content-Type: text/html\n'
815        sys.stderr.seek(0)
816        e = sys.stderr.read()
[157]817        sys.stderr=sys.stdout
[153]818        print error(operation, u, fields, err, e)
[145]819    except InvalidInput, err:
[153]820        print 'Content-Type: text/html\n'
821        sys.stderr.seek(0)
822        e = sys.stderr.read()
[157]823        sys.stderr=sys.stdout
[153]824        print invalidInput(operation, u, fields, err, e)
825    except:
826        print 'Content-Type: text/plain\n'
827        sys.stderr.seek(0)
828        e = sys.stderr.read()
829        print e
830        print '----'
[157]831        sys.stderr = sys.stdout
[153]832        raise
Note: See TracBrowser for help on using the repository browser.