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

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

Add support for
(A) modifying VM names, memory, disksize
(B) transferring ownership, hostname

Has issues with multiple disks/nics because of current interface.

(tabbott is the real author here)

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