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

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

Leave open the option of administrator acls.

  • 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.administrator = user.username
299        machine.contact = user.email
300        machine.uuid = uuidToString(randomUUID())
301        machine.boot_off_cd = True
302        machine_type = Type.get_by(hvm=is_hvm)
303        machine.type_id = machine_type.type_id
304        ctx.current.save(machine)
305        disk = Disk(machine.machine_id, 
306                    'hda', disk)
307        open = NIC.select_by(machine_id=None)
308        if not open: #No IPs left!
309            raise CodeError("No IP addresses left!  Contact sipb-xen-dev@mit.edu")
310        nic = open[0]
311        nic.machine_id = machine.machine_id
312        nic.hostname = name
313        ctx.current.save(nic)   
314        ctx.current.save(disk)
315        transaction.commit()
316    except:
317        transaction.rollback()
318        raise
319    registerMachine(machine)
320    makeDisks(machine)
321    # tell it to boot with cdrom
322    bootMachine(machine, cdrom)
323
324    return machine
325
326def validMemory(user, memory, machine=None, on=True):
327    """Parse and validate limits for memory for a given user and machine.
328
329    on is whether the memory must be valid after the machine is
330    switched on.
331    """
332    try:
333        memory = int(memory)
334        if memory < MIN_MEMORY_SINGLE:
335            raise ValueError
336    except ValueError:
337        raise InvalidInput('memory', memory, 
338                           "Minimum %s MB" % MIN_MEMORY_SINGLE)
339    if memory > maxMemory(user, machine, on):
340        raise InvalidInput('memory', memory,
341                           'Maximum %s MB' % maxMemory(user, machine))
342    return memory
343
344def validDisk(user, disk, machine=None):
345    """Parse and validate limits for disk for a given user and machine."""
346    try:
347        disk = float(disk)
348        if disk > maxDisk(user, machine):
349            raise InvalidInput('disk', disk,
350                               "Maximum %s G" % maxDisk(user, machine))
351        disk = int(disk * 1024)
352        if disk < MIN_DISK_SINGLE * 1024:
353            raise ValueError
354    except ValueError:
355        raise InvalidInput('disk', disk,
356                           "Minimum %s GB" % MIN_DISK_SINGLE)
357    return disk
358
359def create(user, fields):
360    """Handler for create requests."""
361    name = fields.getfirst('name')
362    if not validMachineName(name):
363        raise InvalidInput('name', name)
364    name = name.lower()
365
366    if Machine.get_by(name=name):
367        raise InvalidInput('name', name,
368                           "Already exists")
369   
370    memory = fields.getfirst('memory')
371    memory = validMemory(user, memory, on=True)
372   
373    disk = fields.getfirst('disk')
374    disk = validDisk(user, disk)
375
376    vm_type = fields.getfirst('vmtype')
377    if vm_type not in ('hvm', 'paravm'):
378        raise CodeError("Invalid vm type '%s'"  % vm_type)   
379    is_hvm = (vm_type == 'hvm')
380
381    cdrom = fields.getfirst('cdrom')
382    if cdrom is not None and not CDROM.get(cdrom):
383        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
384   
385    machine = createVm(user, name, memory, disk, is_hvm, cdrom)
386    d = dict(user=user,
387             machine=machine)
388    return Template(file='create.tmpl',
389                   searchList=[d, global_dict]);
390
391def listVms(user, fields):
392    """Handler for list requests."""
393    machines = [m for m in Machine.select() if haveAccess(user, m)]   
394    on = {}
395    has_vnc = {}
396    on = g.uptimes
397    for m in machines:
398        if not on[m]:
399            has_vnc[m] = 'Off'
400        elif m.type.hvm:
401            has_vnc[m] = True
402        else:
403            has_vnc[m] = "ParaVM"+helppopup("paravm_console")
404    #     for m in machines:
405    #         status = statusInfo(m)
406    #         on[m.name] = status is not None
407    #         has_vnc[m.name] = hasVnc(status)
408    max_mem=maxMemory(user)
409    max_disk=maxDisk(user)
410    d = dict(user=user,
411             can_add_vm=canAddVm(user),
412             max_mem=max_mem,
413             max_disk=max_disk,
414             default_mem=max_mem,
415             default_disk=min(4.0, max_disk),
416             machines=machines,
417             has_vnc=has_vnc,
418             uptimes=g.uptimes,
419             cdroms=CDROM.select())
420    return Template(file='list.tmpl', searchList=[d, global_dict])
421
422def testMachineId(user, machineId, exists=True):
423    """Parse, validate and check authorization for a given machineId.
424
425    If exists is False, don't check that it exists.
426    """
427    if machineId is None:
428        raise CodeError("No machine ID specified")
429    try:
430        machineId = int(machineId)
431    except ValueError:
432        raise CodeError("Invalid machine ID '%s'" % machineId)
433    machine = Machine.get(machineId)
434    if exists and machine is None:
435        raise CodeError("No such machine ID '%s'" % machineId)
436    if machine is not None and not haveAccess(user, machine):
437        raise CodeError("No access to machine ID '%s'" % machineId)
438    return machine
439
440def vnc(user, fields):
441    """VNC applet page.
442
443    Note that due to same-domain restrictions, the applet connects to
444    the webserver, which needs to forward those requests to the xen
445    server.  The Xen server runs another proxy that (1) authenticates
446    and (2) finds the correct port for the VM.
447
448    You might want iptables like:
449
450    -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
451    -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
452    -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp --dport 10003 -j ACCEPT
453
454    Remember to enable iptables!
455    echo 1 > /proc/sys/net/ipv4/ip_forward
456    """
457    machine = testMachineId(user, fields.getfirst('machine_id'))
458   
459    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
460
461    data = {}
462    data["user"] = user.username
463    data["machine"]=machine.name
464    data["expires"]=time.time()+(5*60)
465    pickledData = cPickle.dumps(data)
466    m = hmac.new(TOKEN_KEY, digestmod=sha)
467    m.update(pickledData)
468    token = {'data': pickledData, 'digest': m.digest()}
469    token = cPickle.dumps(token)
470    token = base64.urlsafe_b64encode(token)
471   
472    status = statusInfo(machine)
473    has_vnc = hasVnc(status)
474   
475    d = dict(user=user,
476             on=status,
477             has_vnc=has_vnc,
478             machine=machine,
479             hostname=os.environ.get('SERVER_NAME', 'localhost'),
480             authtoken=token)
481    return Template(file='vnc.tmpl',
482                   searchList=[d, global_dict])
483
484def getNicInfo(data_dict, machine):
485    """Helper function for info, get data on nics for a machine.
486
487    Modifies data_dict to include the relevant data, and returns a list
488    of (key, name) pairs to display "name: data_dict[key]" to the user.
489    """
490    data_dict['num_nics'] = len(machine.nics)
491    nic_fields_template = [('nic%s_hostname', 'NIC %s hostname'),
492                           ('nic%s_mac', 'NIC %s MAC Addr'),
493                           ('nic%s_ip', 'NIC %s IP'),
494                           ]
495    nic_fields = []
496    for i in range(len(machine.nics)):
497        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
498        data_dict['nic%s_hostname' % i] = machine.nics[i].hostname + '.servers.csail.mit.edu'
499        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
500        data_dict['nic%s_ip' % i] = machine.nics[i].ip
501    if len(machine.nics) == 1:
502        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
503    return nic_fields
504
505def getDiskInfo(data_dict, machine):
506    """Helper function for info, get data on disks for a machine.
507
508    Modifies data_dict to include the relevant data, and returns a list
509    of (key, name) pairs to display "name: data_dict[key]" to the user.
510    """
511    data_dict['num_disks'] = len(machine.disks)
512    disk_fields_template = [('%s_size', '%s size')]
513    disk_fields = []
514    for disk in machine.disks:
515        name = disk.guest_device_name
516        disk_fields.extend([(x % name, y % name) for x, y in disk_fields_template])
517        data_dict['%s_size' % name] = "%0.1f GB" % (disk.size / 1024.)
518    return disk_fields
519
520def deleteVM(machine):
521    """Delete a VM."""
522    remctl('destroy', machine.name, err=True)
523    transaction = ctx.current.create_transaction()
524    delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
525    try:
526        for nic in machine.nics:
527            nic.machine_id = None
528            nic.hostname = None
529            ctx.current.save(nic)
530        for disk in machine.disks:
531            ctx.current.delete(disk)
532        ctx.current.delete(machine)
533        transaction.commit()
534    except:
535        transaction.rollback()
536        raise
537    for mname, dname in delete_disk_pairs:
538        remctl('web', 'lvremove', mname, dname)
539    unregisterMachine(machine)
540
541def command(user, fields):
542    """Handler for running commands like boot and delete on a VM."""
543    print >> sys.stderr, time.time()-start_time
544    machine = testMachineId(user, fields.getfirst('machine_id'))
545    action = fields.getfirst('action')
546    cdrom = fields.getfirst('cdrom')
547    print >> sys.stderr, time.time()-start_time
548    if cdrom is not None and not CDROM.get(cdrom):
549        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
550    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
551        raise CodeError("Invalid action '%s'" % action)
552    if action == 'Reboot':
553        if cdrom is not None:
554            remctl('reboot', machine.name, cdrom)
555        else:
556            remctl('reboot', machine.name)
557    elif action == 'Power on':
558        if maxMemory(user) < machine.memory:
559            raise InvalidInput('action', 'Power on',
560                               "You don't have enough free RAM quota to turn on this machine")
561        bootMachine(machine, cdrom)
562    elif action == 'Power off':
563        remctl('destroy', machine.name)
564    elif action == 'Shutdown':
565        remctl('shutdown', machine.name)
566    elif action == 'Delete VM':
567        deleteVM(machine)
568    print >> sys.stderr, time.time()-start_time
569
570    d = dict(user=user,
571             command=action,
572             machine=machine)
573    return Template(file="command.tmpl", searchList=[d, global_dict])
574
575def testOwner(user, owner, machine=None):
576    if owner == machine.owner:   #XXX What do we do when you lose access to the locker?
577        return owner
578    value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
579    if value == True:
580        return owner
581    raise InvalidInput('owner', owner, value)
582
583def testContact(user, contact, machine=None):
584    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
585        raise InvalidInput('contact', contact, "Not a valid email")
586    return contact
587
588def testDisk(user, disksize, machine=None):
589    return disksize
590
591def testName(user, name, machine=None):
592    if name is None:
593        return None
594    if not Machine.select_by(name=name):
595        return name
596    if name == machine.name:
597        return name
598    raise InvalidInput('name', name, "Already taken")
599
600def testHostname(user, hostname, machine):
601    for nic in machine.nics:
602        if hostname == nic.hostname:
603            return hostname
604    # check if doesn't already exist
605    if NIC.select_by(hostname=hostname):
606        raise InvalidInput('hostname', hostname,
607                           "Already exists")
608    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
609        raise InvalidInput('hostname', hostname, "Not a valid hostname; must only use number, letters, and dashes.")
610    return hostname
611
612def modify(user, fields):
613    """Handler for modifying attributes of a machine."""
614
615    olddisk = {}
616    transaction = ctx.current.create_transaction()
617    try:
618        machine = testMachineId(user, fields.getfirst('machine_id'))
619        owner = testOwner(user, fields.getfirst('owner'), machine)
620        contact = testContact(user, fields.getfirst('contact'))
621        hostname = testHostname(owner, fields.getfirst('hostname'),
622                                machine)
623        name = testName(user, fields.getfirst('name'), machine)
624        oldname = machine.name
625        command="modify"
626
627        memory = fields.getfirst('memory')
628        if memory is not None:
629            memory = validMemory(user, memory, machine, on=False)
630            machine.memory = memory
631 
632        disksize = testDisk(user, fields.getfirst('disk'))
633        if disksize is not None:
634            disksize = validDisk(user, disksize, machine)
635            disk = machine.disks[0]
636            if disk.size != disksize:
637                olddisk[disk.guest_device_name] = disksize
638                disk.size = disksize
639                ctx.current.save(disk)
640       
641        # XXX first NIC gets hostname on change?  Interface doesn't support more.
642        for nic in machine.nics[:1]:
643            nic.hostname = hostname
644            ctx.current.save(nic)
645
646        if owner is not None and owner != machine.owner:
647            machine.owner = owner
648        if name is not None and name != machine.name:
649            machine.name = name
650           
651        ctx.current.save(machine)
652        transaction.commit()
653    except:
654        transaction.rollback()
655        raise
656    for diskname in olddisk:
657        remctl("web", "lvresize", oldname, diskname, str(olddisk[diskname]))
658    if name is not None and name != oldname:
659        for disk in machine.disks:
660            if oldname != name:
661                remctl("web", "lvrename", oldname, disk.guest_device_name, name)
662        remctl("web", "moveregister", oldname, name)
663    d = dict(user=user,
664             command=command,
665             machine=machine)
666    return Template(file="command.tmpl", searchList=[d, global_dict])   
667
668
669def help(user, fields):
670    """Handler for help messages."""
671    simple = fields.getfirst('simple')
672    subjects = fields.getlist('subject')
673   
674    mapping = dict(paravm_console="""
675ParaVM machines do not support console access over VNC.  To access
676these machines, you either need to boot with a liveCD and ssh in or
677hope that the sipb-xen maintainers add support for serial consoles.""",
678                   hvm_paravm="""
679HVM machines use the virtualization features of the processor, while
680ParaVM machines use Xen's emulation of virtualization features.  You
681want an HVM virtualized machine.""",
682                   cpu_weight="""Don't ask us!  We're as mystified as you are.""",
683                   owner="""The Owner must be the name of a locker that you are an AFS
684administrator of.  In particular, you or an AFS group you are a member
685of must have AFS rlidwka bits on the locker.  You can check see who
686administers the LOCKER locker using the command 'fs la /mit/LOCKER' on
687Athena.)""")
688   
689    d = dict(user=user,
690             simple=simple,
691             subjects=subjects,
692             mapping=mapping)
693   
694    return Template(file="help.tmpl", searchList=[d, global_dict])
695   
696
697def info(user, fields):
698    """Handler for info on a single VM."""
699    machine = testMachineId(user, fields.getfirst('machine_id'))
700    status = statusInfo(machine)
701    has_vnc = hasVnc(status)
702    if status is None:
703        main_status = dict(name=machine.name,
704                           memory=str(machine.memory))
705        uptime=None
706        cputime=None
707    else:
708        main_status = dict(status[1:])
709        start_time = float(main_status.get('start_time', 0))
710        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
711        cpu_time_float = float(main_status.get('cpu_time', 0))
712        cputime = datetime.timedelta(seconds=int(cpu_time_float))
713    display_fields = """name uptime memory state cpu_weight on_reboot
714     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
715    display_fields = [('name', 'Name'),
716                      ('owner', 'Owner'),
717                      ('contact', 'Contact'),
718                      ('type', 'Type'),
719                      'NIC_INFO',
720                      ('uptime', 'uptime'),
721                      ('cputime', 'CPU usage'),
722                      ('memory', 'RAM'),
723                      'DISK_INFO',
724                      ('state', 'state (xen format)'),
725                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
726                      ('on_reboot', 'Action on VM reboot'),
727                      ('on_poweroff', 'Action on VM poweroff'),
728                      ('on_crash', 'Action on VM crash'),
729                      ('on_xend_start', 'Action on Xen start'),
730                      ('on_xend_stop', 'Action on Xen stop'),
731                      ('bootloader', 'Bootloader options'),
732                      ]
733    fields = []
734    machine_info = {}
735    machine_info['name'] = machine.name
736    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
737    machine_info['owner'] = machine.owner
738    machine_info['contact'] = machine.contact
739
740    nic_fields = getNicInfo(machine_info, machine)
741    nic_point = display_fields.index('NIC_INFO')
742    display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
743
744    disk_fields = getDiskInfo(machine_info, machine)
745    disk_point = display_fields.index('DISK_INFO')
746    display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
747   
748    main_status['memory'] += ' MB'
749    for field, disp in display_fields:
750        if field in ('uptime', 'cputime') and locals()[field] is not None:
751            fields.append((disp, locals()[field]))
752        elif field in machine_info:
753            fields.append((disp, machine_info[field]))
754        elif field in main_status:
755            fields.append((disp, main_status[field]))
756        else:
757            pass
758            #fields.append((disp, None))
759    max_mem = maxMemory(user, machine)
760    max_disk = maxDisk(user, machine)
761    d = dict(user=user,
762             cdroms=CDROM.select(),
763             on=status is not None,
764             machine=machine,
765             has_vnc=has_vnc,
766             uptime=str(uptime),
767             ram=machine.memory,
768             max_mem=max_mem,
769             max_disk=max_disk,
770             owner_help=helppopup("owner"),
771             fields = fields)
772    return Template(file='info.tmpl',
773                   searchList=[d, global_dict])
774
775mapping = dict(list=listVms,
776               vnc=vnc,
777               command=command,
778               modify=modify,
779               info=info,
780               create=create,
781               help=help)
782
783if __name__ == '__main__':
784    start_time = time.time()
785    fields = cgi.FieldStorage()
786    class User:
787        username = "moo"
788        email = 'moo@cow.com'
789    u = User()
790    g = Global(u)
791    if 'SSL_CLIENT_S_DN_Email' in os.environ:
792        username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
793        u.username = username
794        u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
795    else:
796        u.username = 'moo'
797        u.email = 'nobody'
798    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
799    operation = os.environ.get('PATH_INFO', '')
800#    print 'Content-Type: text/plain\n'
801#    print operation
802    if not operation:
803        print "Status: 301 Moved Permanently"
804        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
805        sys.exit(0)
806
807    if operation.startswith('/'):
808        operation = operation[1:]
809    if not operation:
810        operation = 'list'
811
812    def badOperation(u, e):
813        raise CodeError("Unknown operation")
814
815    fun = mapping.get(operation, badOperation)
816    if fun not in (help, ):
817        connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
818    try:
819        output = fun(u, fields)
820        print 'Content-Type: text/html\n'
821        sys.stderr.seek(0)
822        e = sys.stderr.read()
823        sys.stderr=sys.stdout
824        if e:
825            output = str(output)
826            output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
827        print output
828    except CodeError, err:
829        print 'Content-Type: text/html\n'
830        sys.stderr.seek(0)
831        e = sys.stderr.read()
832        sys.stderr=sys.stdout
833        print error(operation, u, fields, err, e)
834    except InvalidInput, err:
835        print 'Content-Type: text/html\n'
836        sys.stderr.seek(0)
837        e = sys.stderr.read()
838        sys.stderr=sys.stdout
839        print invalidInput(operation, u, fields, err, e)
840    except:
841        print 'Content-Type: text/plain\n'
842        sys.stderr.seek(0)
843        e = sys.stderr.read()
844        print e
845        print '----'
846        sys.stderr = sys.stdout
847        raise
Note: See TracBrowser for help on using the repository browser.