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

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

minor bugfix, and check not reusing names.

  • Property svn:executable set to *
File size: 27.9 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        super(InvalidInput, self).__init__(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    try:
515        remctl('destroy', machine.name)
516    except:
517        pass
518    transaction = ctx.current.create_transaction()
519    delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
520    try:
521        for nic in machine.nics:
522            nic.machine_id = None
523            nic.hostname = None
524            ctx.current.save(nic)
525        for disk in machine.disks:
526            ctx.current.delete(disk)
527        ctx.current.delete(machine)
528        transaction.commit()
529    except:
530        transaction.rollback()
531        raise
532    for mname, dname in delete_disk_pairs:
533        remctl('web', 'lvremove', mname, dname)
534    unregisterMachine(machine)
535
536def command(user, fields):
537    """Handler for running commands like boot and delete on a VM."""
538    print >> sys.stderr, time.time()-start_time
539    machine = testMachineId(user, fields.getfirst('machine_id'))
540    action = fields.getfirst('action')
541    cdrom = fields.getfirst('cdrom')
542    print >> sys.stderr, time.time()-start_time
543    if cdrom is not None and not CDROM.get(cdrom):
544        raise CodeError("Invalid cdrom type '%s'" % cdrom)   
545    if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
546        raise CodeError("Invalid action '%s'" % action)
547    if action == 'Reboot':
548        if cdrom is not None:
549            remctl('reboot', machine.name, cdrom)
550        else:
551            remctl('reboot', machine.name)
552    elif action == 'Power on':
553        if maxMemory(user) < machine.memory:
554            raise InvalidInput('action', 'Power on',
555                               "You don't have enough free RAM quota to turn on this machine")
556        bootMachine(machine, cdrom)
557    elif action == 'Power off':
558        remctl('destroy', machine.name)
559    elif action == 'Shutdown':
560        remctl('shutdown', machine.name)
561    elif action == 'Delete VM':
562        deleteVM(machine)
563    print >> sys.stderr, time.time()-start_time
564
565    d = dict(user=user,
566             command=action,
567             machine=machine)
568    return Template(file="command.tmpl", searchList=[d, global_dict])
569
570def testOwner(user, owner, machine=None):
571    if not getafsgroups.checkLockerOwner(user.username, owner):
572        raise InvalidInput('owner', owner,
573                           "Invalid")
574    return owner
575
576def testContact(user, contact, machine=None):
577    if contact != user.email:
578        raise InvalidInput('contact', contact,
579                           "Invalid")
580    return contact
581
582def testDisk(user, disksize, machine=None):
583    return disksize
584
585def testName(user, name, machine=None):
586    if Machine.select_by(name=name) == []:
587        return name
588    raise InvalidInput('name', name,
589                       "Already taken")
590
591def testHostname(user, hostname, machine):
592    for nic in machine.nics:
593        if hostname == nic.hostname:
594            return hostname
595    # check if doesn't already exist
596    if NIC.select_by(hostname=hostname) == []:
597        return hostname
598    raise InvalidInput('hostname', hostname,
599                       "Different from before")
600
601
602def modify(user, fields):
603    """Handler for modifying attributes of a machine."""
604    #XXX not written yet
605
606    transaction = ctx.current.create_transaction()
607    try:
608        machine = testMachineId(user, fields.getfirst('machine_id'))
609        owner = testOwner(user, fields.getfirst('owner'), machine)
610        contact = testContact(user, fields.getfirst('contact'))
611        hostname = testHostname(owner, fields.getfirst('hostname'),
612                            machine)
613        name = testName(user, fields.getfirst('name'))
614        oldname = machine.name
615        olddisk = {}
616
617        memory = fields.getfirst('memory')
618        if memory is not None:
619            memory = validMemory(user, memory, machine)
620        if memory != machine.memory:
621            machine.memory = memory
622
623        disksize = testDisk(user, fields.getfirst('disk'))
624        if disksize is not None:
625            disksize = validDisk(user, disksize, machine)
626       
627        for disk in machine.disks:
628            disk.size = disksize
629            olddisk[disk.guest_device_name] = disk.size
630            ctx.current.save(disk)
631       
632        # XXX all NICs get same hostname on change?  Interface doesn't support more.
633        for nic in machine.nics:
634            nic.hostname = hostname
635            ctx.current.save(nic)
636
637        if owner != machine.owner:
638            machine.owner = owner
639        if name != machine.name:
640            machine.name = name
641           
642        ctx.current.save(machine)
643        transaction.commit()
644    except:
645        transaction.rollback()
646        raise
647    remctl("web", "moveregister", oldname, name)
648    for disk in machine.disks:
649        # XXX all disks get the same size on change?  Interface doesn't support more.
650        if disk.size != olddisk[disk.guest_device_name]:
651            remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size))
652        if oldname != name:
653            remctl("web", "lvrename", oldname, disk.guest_device_name, name)
654    d = dict(user=user,
655             command="modify",
656             machine=machine)
657    return Template(file="command.tmpl", searchList=[d, global_dict])   
658
659
660def help(user, fields):
661    """Handler for help messages."""
662    simple = fields.getfirst('simple')
663    subjects = fields.getlist('subject')
664   
665    mapping = dict(paravm_console="""
666ParaVM machines do not support console access over VNC.  To access
667these machines, you either need to boot with a liveCD and ssh in or
668hope that the sipb-xen maintainers add support for serial consoles.""",
669                   hvm_paravm="""
670HVM machines use the virtualization features of the processor, while
671ParaVM machines use Xen's emulation of virtualization features.  You
672want an HVM virtualized machine.""",
673                   cpu_weight="""Don't ask us!  We're as mystified as you are.""")
674   
675    d = dict(user=user,
676             simple=simple,
677             subjects=subjects,
678             mapping=mapping)
679   
680    return Template(file="help.tmpl", searchList=[d, global_dict])
681   
682
683def info(user, fields):
684    """Handler for info on a single VM."""
685    machine = testMachineId(user, fields.getfirst('machine_id'))
686    status = statusInfo(machine)
687    has_vnc = hasVnc(status)
688    if status is None:
689        main_status = dict(name=machine.name,
690                           memory=str(machine.memory))
691    else:
692        main_status = dict(status[1:])
693    start_time = float(main_status.get('start_time', 0))
694    uptime = datetime.timedelta(seconds=int(time.time()-start_time))
695    cpu_time_float = float(main_status.get('cpu_time', 0))
696    cputime = datetime.timedelta(seconds=int(cpu_time_float))
697    display_fields = """name uptime memory state cpu_weight on_reboot
698     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
699    display_fields = [('name', 'Name'),
700                      ('owner', 'Owner'),
701                      ('contact', 'Contact'),
702                      ('type', 'Type'),
703                      'NIC_INFO',
704                      ('uptime', 'uptime'),
705                      ('cputime', 'CPU usage'),
706                      ('memory', 'RAM'),
707                      'DISK_INFO',
708                      ('state', 'state (xen format)'),
709                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
710                      ('on_reboot', 'Action on VM reboot'),
711                      ('on_poweroff', 'Action on VM poweroff'),
712                      ('on_crash', 'Action on VM crash'),
713                      ('on_xend_start', 'Action on Xen start'),
714                      ('on_xend_stop', 'Action on Xen stop'),
715                      ('bootloader', 'Bootloader options'),
716                      ]
717    fields = []
718    machine_info = {}
719    machine_info['name'] = machine.name
720    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
721    machine_info['owner'] = machine.owner
722    machine_info['contact'] = machine.contact
723
724    nic_fields = getNicInfo(machine_info, machine)
725    nic_point = display_fields.index('NIC_INFO')
726    display_fields = display_fields[:nic_point] + nic_fields + display_fields[nic_point+1:]
727
728    disk_fields = getDiskInfo(machine_info, machine)
729    disk_point = display_fields.index('DISK_INFO')
730    display_fields = display_fields[:disk_point] + disk_fields + display_fields[disk_point+1:]
731   
732    main_status['memory'] += ' MB'
733    for field, disp in display_fields:
734        if field in ('uptime', 'cputime'):
735            fields.append((disp, locals()[field]))
736        elif field in machine_info:
737            fields.append((disp, machine_info[field]))
738        elif field in main_status:
739            fields.append((disp, main_status[field]))
740        else:
741            pass
742            #fields.append((disp, None))
743    max_mem = maxMemory(user, machine)
744    max_disk = maxDisk(user, machine)
745    d = dict(user=user,
746             cdroms=CDROM.select(),
747             on=status is not None,
748             machine=machine,
749             has_vnc=has_vnc,
750             uptime=str(uptime),
751             ram=machine.memory,
752             max_mem=max_mem,
753             max_disk=max_disk,
754             fields = fields)
755    return Template(file='info.tmpl',
756                   searchList=[d, global_dict])
757
758mapping = dict(list=listVms,
759               vnc=vnc,
760               command=command,
761               modify=modify,
762               info=info,
763               create=create,
764               help=help)
765
766if __name__ == '__main__':
767    start_time = time.time()
768    fields = cgi.FieldStorage()
769    class User:
770        username = "moo"
771        email = 'moo@cow.com'
772    u = User()
773    g = Global(u)
774    if 'SSL_CLIENT_S_DN_Email' in os.environ:
775        username = os.environ[ 'SSL_CLIENT_S_DN_Email'].split("@")[0]
776        u.username = username
777        u.email = os.environ[ 'SSL_CLIENT_S_DN_Email']
778    else:
779        u.username = 'moo'
780        u.email = 'nobody'
781    connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
782    operation = os.environ.get('PATH_INFO', '')
783#    print 'Content-Type: text/plain\n'
784#    print operation
785    if not operation:
786        print "Status: 301 Moved Permanently"
787        print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
788        sys.exit(0)
789
790    if operation.startswith('/'):
791        operation = operation[1:]
792    if not operation:
793        operation = 'list'
794
795    def badOperation(u, e):
796        raise CodeError("Unknown operation")
797
798    fun = mapping.get(operation, badOperation)
799    if fun not in (help, ):
800        connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
801    try:
802        output = fun(u, fields)
803        print 'Content-Type: text/html\n'
804        sys.stderr.seek(0)
805        e = sys.stderr.read()
806        if e:
807            output = str(output)
808            output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')
809        print output
810    except CodeError, err:
811        print 'Content-Type: text/html\n'
812        sys.stderr.seek(0)
813        e = sys.stderr.read()
814        sys.stderr=sys.stdout
815        print error(operation, u, fields, err, e)
816    except InvalidInput, err:
817        print 'Content-Type: text/html\n'
818        sys.stderr.seek(0)
819        e = sys.stderr.read()
820        sys.stderr=sys.stdout
821        print invalidInput(operation, u, fields, err, e)
822    except:
823        print 'Content-Type: text/plain\n'
824        sys.stderr.seek(0)
825        e = sys.stderr.read()
826        print e
827        print '----'
828        sys.stderr = sys.stdout
829        raise
Note: See TracBrowser for help on using the repository browser.