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

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

Another bare except removed.

  • Property svn:executable set to *
File size: 28.5 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):
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)
101    active_machines = [x for x in machines if g.uptimes[x]]
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
105def maxDisk(user, machine=None):
106    machines = getMachinesByOwner(user.username)
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.)
110
111def canAddVm(user):
112    machines = getMachinesByOwner(user.username)
113    active_machines = [x for x in machines if g.uptimes[x]]
114    return (len(machines) < MAX_VMS_TOTAL and
115            len(active_machines) < MAX_VMS_ACTIVE)
116
117def haveAccess(user, machine):
118    """Return whether a user has access to a machine"""
119    if user.username == 'moo':
120        return True
121    return getafsgroups.checkLockerOwner(user.username,machine.owner)
122
123def error(op, user, fields, err, emsg):
124    """Print an error page when a CodeError occurs"""
125    d = dict(op=op, user=user, errorMessage=str(err),
126             stderr=emsg)
127    return Template(file='error.tmpl', searchList=[d, global_dict]);
128
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
136def validMachineName(name):
137    """Check that name is valid for a machine name"""
138    if not name:
139        return False
140    charset = string.ascii_letters + string.digits + '-_'
141    if name[0] in '-_' or len(name) > 22:
142        return False
143    for x in name:
144        if x not in charset:
145            return False
146    return True
147
148def kinit(username = 'tabbott/extra', keytab = '/etc/tabbott.keytab'):
149    """Kinit with a given username and keytab"""
150
151    p = subprocess.Popen(['kinit', "-k", "-t", keytab, username],
152                         stderr=subprocess.PIPE)
153    e = p.wait()
154    if e:
155        raise CodeError("Error %s in kinit: %s" % (e, p.stderr.read()))
156
157def checkKinit():
158    """If we lack tickets, kinit."""
159    p = subprocess.Popen(['klist', '-s'])
160    if p.wait():
161        kinit()
162
163def remctl(*args, **kws):
164    """Perform a remctl and return the output.
165
166    kinits if necessary, and outputs errors to stderr.
167    """
168    checkKinit()
169    p = subprocess.Popen(['remctl', 'black-mesa.mit.edu']
170                         + list(args),
171                         stdout=subprocess.PIPE,
172                         stderr=subprocess.PIPE)
173    if kws.get('err'):
174        p.wait()
175        return p.stdout.read(), p.stderr.read()
176    if p.wait():
177        raise CodeError('ERROR on remctl %s: %s' %
178                          (args, p.stderr.read()))
179    return p.stdout.read()
180
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)
190
191def bootMachine(machine, cdtype):
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    """
197    if cdtype is not None:
198        remctl('web', 'vmboot', machine.name,
199               cdtype)
200    else:
201        remctl('web', 'vmboot', machine.name)
202
203def registerMachine(machine):
204    """Register a machine to be controlled by the web interface"""
205    remctl('web', 'register', machine.name)
206
207def unregisterMachine(machine):
208    """Unregister a machine to not be controlled by the web interface"""
209    remctl('web', 'unregister', machine.name)
210
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 == ')':
225            if len(stack[-1]) == 1:
226                stack[-1].append('')
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
235def getUptimes(machines=None):
236    """Return a dictionary mapping machine names to uptime strings"""
237    value_string = remctl('web', 'listvms')
238    lines = value_string.splitlines()
239    d = {}
240    for line in lines:
241        lst = line.split()
242        name, id = lst[:2]
243        uptime = ' '.join(lst[2:])
244        d[name] = uptime
245    ans = {}
246    for m in machines:
247        ans[m] = d.get(m.name)
248    return ans
249
250def statusInfo(machine):
251    """Return the status list for a given machine.
252
253    Gets and parses xm list --long
254    """
255    value_string, err_string = remctl('list-long', machine.name, err=True)
256    if 'Unknown command' in err_string:
257        raise CodeError("ERROR in remctl list-long %s is not registered" % (machine.name,))
258    elif 'does not exist' in err_string:
259        return None
260    elif err_string:
261        raise CodeError("ERROR in remctl list-long %s%s" % (machine.name, err_string))
262    status = parseStatus(value_string)
263    return status
264
265def hasVnc(status):
266    """Does the machine with a given status list support VNC?"""
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
275def createVm(user, name, memory, disk, is_hvm, cdrom):
276    """Create a VM and put it in the database"""
277    # put stuff in the table
278    transaction = ctx.current.create_transaction()
279    try:
280        if memory > maxMemory(user):
281            raise InvalidInput('memory', memory,
282                               "Max %s" % maxMemory(user))
283        if disk > maxDisk(user) * 1024:
284            raise InvalidInput('disk', disk,
285                               "Max %s" % maxDisk(user))
286        if not canAddVm(user):
287            raise InvalidInput('create', True, 'Unable to create more VMs')
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!
305            raise CodeError("No IP addresses left!  Contact sipb-xen-dev@mit.edu")
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
315    registerMachine(machine)
316    makeDisks(machine)
317    # tell it to boot with cdrom
318    bootMachine(machine, cdrom)
319
320    return machine
321
322def validMemory(user, memory, machine=None):
323    """Parse and validate limits for memory for a given user and machine."""
324    try:
325        memory = int(memory)
326        if memory < MIN_MEMORY_SINGLE:
327            raise ValueError
328    except ValueError:
329        raise InvalidInput('memory', memory, 
330                           "Minimum %s MB" % MIN_MEMORY_SINGLE)
331    if memory > maxMemory(user, machine):
332        raise InvalidInput('memory', memory,
333                           'Maximum %s MB' % maxMemory(user, machine))
334    return memory
335
336def validDisk(user, disk, machine=None):
337    """Parse and validate limits for disk for a given user and machine."""
338    try:
339        disk = float(disk)
340        if disk > maxDisk(user, machine):
341            raise InvalidInput('disk', disk,
342                               "Maximum %s G" % maxDisk(user, machine))
343        disk = int(disk * 1024)
344        if disk < MIN_DISK_SINGLE * 1024:
345            raise ValueError
346    except ValueError:
347        raise InvalidInput('disk', disk,
348                           "Minimum %s GB" % MIN_DISK_SINGLE)
349    return disk
350
351def create(user, fields):
352    """Handler for create requests."""
353    name = fields.getfirst('name')
354    if not validMachineName(name):
355        raise InvalidInput('name', name)
356    name = name.lower()
357
358    if Machine.get_by(name=name):
359        raise InvalidInput('name', name,
360                           "Already exists")
361   
362    memory = fields.getfirst('memory')
363    memory = validMemory(user, memory)
364   
365    disk = fields.getfirst('disk')
366    disk = validDisk(user, disk)
367
368    vm_type = fields.getfirst('vmtype')
369    if vm_type not in ('hvm', 'paravm'):
370        raise CodeError("Invalid vm type '%s'"  % vm_type)   
371    is_hvm = (vm_type == 'hvm')
372
373    cdrom = fields.getfirst('cdrom')
374    if cdrom is not None and not CDROM.get(cdrom):
375        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
376   
377    machine = createVm(user, name, memory, disk, is_hvm, cdrom)
378    d = dict(user=user,
379             machine=machine)
380    return Template(file='create.tmpl',
381                   searchList=[d, global_dict]);
382
383def listVms(user, fields):
384    """Handler for list requests."""
385    machines = [m for m in Machine.select() if haveAccess(user, m)]   
386    on = {}
387    has_vnc = {}
388    on = g.uptimes
389    for m in machines:
390        if not on[m]:
391            has_vnc[m] = 'Off'
392        elif m.type.hvm:
393            has_vnc[m] = True
394        else:
395            has_vnc[m] = "ParaVM"+helppopup("paravm_console")
396    #     for m in machines:
397    #         status = statusInfo(m)
398    #         on[m.name] = status is not None
399    #         has_vnc[m.name] = hasVnc(status)
400    max_mem=maxMemory(user)
401    max_disk=maxDisk(user)
402    d = dict(user=user,
403             can_add_vm=canAddVm(user),
404             max_mem=max_mem,
405             max_disk=max_disk,
406             default_mem=max_mem,
407             default_disk=min(4.0, max_disk),
408             machines=machines,
409             has_vnc=has_vnc,
410             uptimes=g.uptimes,
411             cdroms=CDROM.select())
412    return Template(file='list.tmpl', searchList=[d, global_dict])
413
414def testMachineId(user, machineId, exists=True):
415    """Parse, validate and check authorization for a given machineId.
416
417    If exists is False, don't check that it exists.
418    """
419    if machineId is None:
420        raise CodeError("No machine ID specified")
421    try:
422        machineId = int(machineId)
423    except ValueError:
424        raise CodeError("Invalid machine ID '%s'" % machineId)
425    machine = Machine.get(machineId)
426    if exists and machine is None:
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)
430    return machine
431
432def vnc(user, fields):
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
445
446    Remember to enable iptables!
447    echo 1 > /proc/sys/net/ipv4/ip_forward
448    """
449    machine = testMachineId(user, fields.getfirst('machine_id'))
450   
451    TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
452
453    data = {}
454    data["user"] = user.username
455    data["machine"]=machine.name
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   
464    status = statusInfo(machine)
465    has_vnc = hasVnc(status)
466   
467    d = dict(user=user,
468             on=status,
469             has_vnc=has_vnc,
470             machine=machine,
471             hostname=os.environ.get('SERVER_NAME', 'localhost'),
472             authtoken=token)
473    return Template(file='vnc.tmpl',
474                   searchList=[d, global_dict])
475
476def getNicInfo(data_dict, machine):
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    """
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):
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    """
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):
513    """Delete a VM."""
514    remctl('destroy', machine.name, err=True)
515    transaction = ctx.current.create_transaction()
516    delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
517    try:
518        for nic in machine.nics:
519            nic.machine_id = None
520            nic.hostname = None
521            ctx.current.save(nic)
522        for disk in machine.disks:
523            ctx.current.delete(disk)
524        ctx.current.delete(machine)
525        transaction.commit()
526    except:
527        transaction.rollback()
528        raise
529    for mname, dname in delete_disk_pairs:
530        remctl('web', 'lvremove', mname, dname)
531    unregisterMachine(machine)
532
533def command(user, fields):
534    """Handler for running commands like boot and delete on a VM."""
535    print >> sys.stderr, time.time()-start_time
536    machine = testMachineId(user, fields.getfirst('machine_id'))
537    action = fields.getfirst('action')
538    cdrom = fields.getfirst('cdrom')
539    print >> sys.stderr, time.time()-start_time
540    if cdrom is not None and not CDROM.get(cdrom):
541        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
542    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
543        raise CodeError("Invalid action '%s'" % action)
544    if action == 'Reboot':
545        if cdrom is not None:
546            remctl('reboot', machine.name, cdrom)
547        else:
548            remctl('reboot', machine.name)
549    elif action == 'Power on':
550        if maxMemory(user) < machine.memory:
551            raise InvalidInput('action', 'Power on',
552                               "You don't have enough free RAM quota to turn on this machine")
553        bootMachine(machine, cdrom)
554    elif action == 'Power off':
555        remctl('destroy', machine.name)
556    elif action == 'Shutdown':
557        remctl('shutdown', machine.name)
558    elif action == 'Delete VM':
559        deleteVM(machine)
560    print >> sys.stderr, time.time()-start_time
561
562    d = dict(user=user,
563             command=action,
564             machine=machine)
565    return Template(file="command.tmpl", searchList=[d, global_dict])
566
567def testOwner(user, owner, machine=None):
568    if not getafsgroups.checkLockerOwner(user.username, owner):
569        raise InvalidInput('owner', owner,
570                           "Invalid")
571    return owner
572
573def testContact(user, contact, machine=None):
574    if contact != user.email:
575        raise InvalidInput('contact', contact,
576                           "Invalid")
577    return contact
578
579def testDisk(user, disksize, machine=None):
580    return disksize
581
582def testName(user, name, machine=None):
583    if Machine.select_by(name=name) == []:
584        return name
585    if name == machine.name:
586        return name
587    raise InvalidInput('name', name,
588                       "Already taken")
589
590def testHostname(user, hostname, machine):
591    for nic in machine.nics:
592        if hostname == nic.hostname:
593            return hostname
594    # check if doesn't already exist
595    if NIC.select_by(hostname=hostname) == []:
596        return hostname
597    raise InvalidInput('hostname', hostname,
598                       "Different from before")
599
600
601def modify(user, fields):
602    """Handler for modifying attributes of a machine."""
603    #XXX not written yet
604
605    transaction = ctx.current.create_transaction()
606    try:
607        machine = testMachineId(user, fields.getfirst('machine_id'))
608        owner = testOwner(user, fields.getfirst('owner'), machine)
609        contact = testContact(user, fields.getfirst('contact'))
610        hostname = testHostname(owner, fields.getfirst('hostname'),
611                            machine)
612        name = testName(user, fields.getfirst('name'), machine)
613        oldname = machine.name
614        command="modify"
615        olddisk = {}
616
617        memory = fields.getfirst('memory')
618        if memory is not None:
619            memory = validMemory(user, memory, machine)
620        else:
621            memory = machine.memory
622        if memory != machine.memory:
623            machine.memory = memory
624
625        disksize = testDisk(user, fields.getfirst('disk'))
626        if disksize is not None:
627            disksize = validDisk(user, disksize, machine)
628        else:
629            disksize = machine.disks[0].size
630        for disk in machine.disks:
631            olddisk[disk.guest_device_name] = disk.size
632            disk.size = disksize
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()
649        raise
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=command,
659             machine=machine)
660    return Template(file="command.tmpl", searchList=[d, global_dict])   
661
662
663def help(user, fields):
664    """Handler for help messages."""
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                   owner="""The Owner must be the name of a locker that you are an AFS
678administrator of.  In particular, you or an AFS group you are a member
679of must have AFS rlidwka bits on the locker.  You can check see who
680administers the LOCKER locker using the command 'fs la /mit/LOCKER' on
681Athena.)""")
682   
683    d = dict(user=user,
684             simple=simple,
685             subjects=subjects,
686             mapping=mapping)
687   
688    return Template(file="help.tmpl", searchList=[d, global_dict])
689   
690
691def info(user, fields):
692    """Handler for info on a single VM."""
693    machine = testMachineId(user, fields.getfirst('machine_id'))
694    status = statusInfo(machine)
695    has_vnc = hasVnc(status)
696    if status is None:
697        main_status = dict(name=machine.name,
698                           memory=str(machine.memory))
699        uptime=None
700        cputime=None
701    else:
702        main_status = dict(status[1:])
703        start_time = float(main_status.get('start_time', 0))
704        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
705        cpu_time_float = float(main_status.get('cpu_time', 0))
706        cputime = datetime.timedelta(seconds=int(cpu_time_float))
707    display_fields = """name uptime memory state cpu_weight on_reboot
708     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
709    display_fields = [('name', 'Name'),
710                      ('owner', 'Owner'),
711                      ('contact', 'Contact'),
712                      ('type', 'Type'),
713                      'NIC_INFO',
714                      ('uptime', 'uptime'),
715                      ('cputime', 'CPU usage'),
716                      ('memory', 'RAM'),
717                      'DISK_INFO',
718                      ('state', 'state (xen format)'),
719                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
720                      ('on_reboot', 'Action on VM reboot'),
721                      ('on_poweroff', 'Action on VM poweroff'),
722                      ('on_crash', 'Action on VM crash'),
723                      ('on_xend_start', 'Action on Xen start'),
724                      ('on_xend_stop', 'Action on Xen stop'),
725                      ('bootloader', 'Bootloader options'),
726                      ]
727    fields = []
728    machine_info = {}
729    machine_info['name'] = machine.name
730    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
731    machine_info['owner'] = machine.owner
732    machine_info['contact'] = machine.contact
733
734    nic_fields = getNicInfo(machine_info, machine)
735    nic_point = display_fields.index('NIC_INFO')
736    display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
737
738    disk_fields = getDiskInfo(machine_info, machine)
739    disk_point = display_fields.index('DISK_INFO')
740    display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
741   
742    main_status['memory'] += ' MB'
743    for field, disp in display_fields:
744        if field in ('uptime', 'cputime') and locals()[field] is not None:
745            fields.append((disp, locals()[field]))
746        elif field in machine_info:
747            fields.append((disp, machine_info[field]))
748        elif field in main_status:
749            fields.append((disp, main_status[field]))
750        else:
751            pass
752            #fields.append((disp, None))
753    max_mem = maxMemory(user, machine)
754    max_disk = maxDisk(user, machine)
755    d = dict(user=user,
756             cdroms=CDROM.select(),
757             on=status is not None,
758             machine=machine,
759             has_vnc=has_vnc,
760             uptime=str(uptime),
761             ram=machine.memory,
762             max_mem=max_mem,
763             max_disk=max_disk,
764             owner_help=helppopup("owner"),
765             fields = fields)
766    return Template(file='info.tmpl',
767                   searchList=[d, global_dict])
768
769mapping = dict(list=listVms,
770               vnc=vnc,
771               command=command,
772               modify=modify,
773               info=info,
774               create=create,
775               help=help)
776
777if __name__ == '__main__':
778    start_time = time.time()
779    fields = cgi.FieldStorage()
780    class User:
781        username = "moo"
782        email = 'moo@cow.com'
783    u = User()
784    g = Global(u)
785    if 'SSL_CLIENT_S_DN_Email' in os.environ:
786        username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
787        u.username = username
788        u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
789    else:
790        u.username = 'moo'
791        u.email = 'nobody'
792    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
793    operation = os.environ.get('PATH_INFO', '')
794#    print 'Content-Type: text/plain\n'
795#    print operation
796    if not operation:
797        print "Status: 301 Moved Permanently"
798        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
799        sys.exit(0)
800
801    if operation.startswith('/'):
802        operation = operation[1:]
803    if not operation:
804        operation = 'list'
805
806    def badOperation(u, e):
807        raise CodeError("Unknown operation")
808
809    fun = mapping.get(operation, badOperation)
810    if fun not in (help, ):
811        connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
812    try:
813        output = fun(u, fields)
814        print 'Content-Type: text/html\n'
815        sys.stderr.seek(0)
816        e = sys.stderr.read()
817        if e:
818            output = str(output)
819            output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
820        print output
821    except CodeError, err:
822        print 'Content-Type: text/html\n'
823        sys.stderr.seek(0)
824        e = sys.stderr.read()
825        sys.stderr=sys.stdout
826        print error(operation, u, fields, err, e)
827    except InvalidInput, 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 invalidInput(operation, u, fields, err, e)
833    except:
834        print 'Content-Type: text/plain\n'
835        sys.stderr.seek(0)
836        e = sys.stderr.read()
837        print e
838        print '----'
839        sys.stderr = sys.stdout
840        raise
Note: See TracBrowser for help on using the repository browser.