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

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

Several modifications.

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