source: package_branches/invirt-web/cherrypy/code/main.py @ 2527

Last change on this file since 2527 was 2527, checked in by price, 14 years ago

tighten a bit of code

  • Property svn:executable set to *
File size: 28.5 KB
Line 
1#!/usr/bin/python
2"""Main CGI script for web interface"""
3
4from __future__ import with_statement
5
6import base64
7import cPickle
8import cgi
9import datetime
10import hmac
11import random
12import sha
13import sys
14import threading
15import time
16import urllib
17import socket
18import cherrypy
19from cherrypy import _cperror
20from StringIO import StringIO
21
22def printError():
23    """Revert stderr to stdout, and print the contents of stderr"""
24    if isinstance(sys.stderr, StringIO):
25        print revertStandardError()
26
27if __name__ == '__main__':
28    import atexit
29    atexit.register(printError)
30
31import validation
32import cache_acls
33from webcommon import State
34import controls
35from getafsgroups import getAfsGroupMembers
36from invirt import database
37from invirt.database import Machine, CDROM, session, connect, MachineAccess, Type, Autoinstall
38from invirt.config import structs as config
39from invirt.common import InvalidInput, CodeError
40import invirt.remctl
41
42from view import View, revertStandardError
43import ajaxterm
44
45class InvirtUnauthWeb(View):
46    @cherrypy.expose
47    @cherrypy.tools.mako(filename="/unauth.mako")
48    def index(self):
49        return {'simple': True}
50
51class InvirtWeb(View):
52    def __init__(self):
53        super(self.__class__,self).__init__()
54        connect()
55        self._cp_config['tools.require_login.on'] = True
56        self._cp_config['tools.catch_stderr.on'] = True
57        self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config',
58                                                 'from invirt import database']
59        self._cp_config['request.error_response'] = self.handle_error
60
61    @cherrypy.expose
62    @cherrypy.tools.mako(filename="/invalid.mako")
63    def invalidInput(self):
64        """Print an error page when an InvalidInput exception occurs"""
65        err = cherrypy.request.prev.params["err"]
66        emsg = cherrypy.request.prev.params["emsg"]
67        d = dict(err_field=err.err_field,
68                 err_value=str(err.err_value), stderr=emsg,
69                 errorMessage=str(err))
70        return d
71
72    @cherrypy.expose
73    @cherrypy.tools.mako(filename="/error.mako")
74    def error(self):
75        """Print an error page when an exception occurs"""
76        op = cherrypy.request.prev.path_info
77        username = cherrypy.request.login
78        err = cherrypy.request.prev.params["err"]
79        emsg = cherrypy.request.prev.params["emsg"]
80        traceback = cherrypy.request.prev.params["traceback"]
81        d = dict(op=op, user=username, fields=cherrypy.request.prev.params,
82                 errorMessage=str(err), stderr=emsg, traceback=traceback)
83        error_raw = cherrypy.request.lookup.get_template("/error_raw.mako")
84        details = error_raw.render(**d)
85        exclude = config.web.errormail_exclude
86        if username not in exclude and '*' not in exclude:
87            send_error_mail('xvm error on %s for %s: %s' % (op, cherrypy.request.login, err),
88                            details)
89        d['details'] = details
90        return d
91
92    def __getattr__(self, name):
93        if name in ("admin", "overlord"):
94            if not cherrypy.request.login in getAfsGroupMembers(config.adminacl, config.authz[0].cell):
95                raise InvalidInput('username', cherrypy.request.login,
96                                   'Not in admin group %s.' % config.adminacl)
97            cherrypy.request.state = State(cherrypy.request.login, isadmin=True)
98            return self
99        else:
100            return super(InvirtWeb, self).__getattr__(name)
101
102    def handle_error(self):
103        err = sys.exc_info()[1]
104        if isinstance(err, InvalidInput):
105            cherrypy.request.params['err'] = err
106            cherrypy.request.params['emsg'] = revertStandardError()
107            raise cherrypy.InternalRedirect('/invalidInput')
108        if not cherrypy.request.prev or 'err' not in cherrypy.request.prev.params:
109            cherrypy.request.params['err'] = err
110            cherrypy.request.params['emsg'] = revertStandardError()
111            cherrypy.request.params['traceback'] = _cperror.format_exc()
112            raise cherrypy.InternalRedirect('/error')
113        # fall back to cherrypy default error page
114        cherrypy.HTTPError(500).set_response()
115
116    @cherrypy.expose
117    @cherrypy.tools.mako(filename="/list.mako")
118    def list(self, result=None):
119        """Handler for list requests."""
120        checkpoint.checkpoint('Getting list dict')
121        d = getListDict(cherrypy.request.login, cherrypy.request.state)
122        if result is not None:
123            d['result'] = result
124        checkpoint.checkpoint('Got list dict')
125        return d
126    index=list
127
128    @cherrypy.expose
129    @cherrypy.tools.mako(filename="/help.mako")
130    def help(self, subject=None, simple=False):
131        """Handler for help messages."""
132
133        help_mapping = {
134            'Autoinstalls': """
135The autoinstaller builds a minimal Debian or Ubuntu system to run as a
136ParaVM.  You can access the resulting system by logging into the <a
137href="help?simple=true&subject=ParaVM+Console">serial console server</a>
138with your Kerberos tickets; there is no root password so sshd will
139refuse login.</p>
140
141<p>Under the covers, the autoinstaller uses our own patched version of
142xen-create-image, which is a tool based on debootstrap.  If you log
143into the serial console while the install is running, you can watch
144it.
145""",
146            'ParaVM Console': """
147ParaVM machines do not support local console access over VNC.  To
148access the serial console of these machines, you can SSH with Kerberos
149to %s, using the name of the machine as your
150username.""" % config.console.hostname,
151            'HVM/ParaVM': """
152HVM machines use the virtualization features of the processor, while
153ParaVM machines rely on a modified kernel to communicate directly with
154the hypervisor.  HVMs support boot CDs of any operating system, and
155the VNC console applet.  The three-minute autoinstaller produces
156ParaVMs.  ParaVMs typically are more efficient, and always support the
157<a href="help?subject=ParaVM+Console">console server</a>.</p>
158
159<p>More details are <a
160href="https://xvm.scripts.mit.edu/wiki/Paravirtualization">on the
161wiki</a>, including steps to prepare an HVM guest to boot as a ParaVM
162(which you can skip by using the autoinstaller to begin with.)</p>
163
164<p>We recommend using a ParaVM when possible and an HVM when necessary.
165""",
166            'CPU Weight': """
167Don't ask us!  We're as mystified as you are.""",
168            'Owner': """
169The owner field is used to determine <a
170href="help?subject=Quotas">quotas</a>.  It must be the name of a
171locker that you are an AFS administrator of.  In particular, you or an
172AFS group you are a member of must have AFS rlidwka bits on the
173locker.  You can check who administers the LOCKER locker using the
174commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.)  See also <a
175href="help?subject=Administrator">administrator</a>.""",
176            'Administrator': """
177The administrator field determines who can access the console and
178power on and off the machine.  This can be either a user or a moira
179group.""",
180            'Quotas': """
181Quotas are determined on a per-locker basis.  Each locker may have a
182maximum of 512 mebibytes of active ram, 50 gibibytes of disk, and 4
183active machines.""",
184            'Console': """
185<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
186setting <tt>fb=false</tt> to disable the framebuffer.  If you don't,
187your machine will run just fine, but the applet's display of the
188console will suffer artifacts.
189""",
190            'Windows': """
191<strong>Windows Vista:</strong> The Vista image is licensed for all MIT students and will automatically activate off the network; see <a href="/static/msca-email.txt">the licensing confirmation e-mail</a> for details. The installer requires 512 MiB RAM and at least 7.5 GiB disk space (15 GiB or more recommended).<br>
192<strong>Windows XP:</strong> This is the volume license CD image. You will need your own volume license key to complete the install. We do not have these available for the general MIT community; ask your department if they have one, or visit <a href="http://msca.mit.edu/">http://msca.mit.edu/</a> if you are staff/faculty to request one.
193"""
194            }
195
196        if not subject:
197            subject = sorted(help_mapping.keys())
198        if not isinstance(subject, list):
199            subject = [subject]
200
201        return dict(simple=simple,
202                    subjects=subject,
203                    mapping=help_mapping)
204    help._cp_config['tools.require_login.on'] = False
205
206    def parseCreate(self, fields):
207        kws = dict([(kw, fields[kw]) for kw in
208         'name description owner memory disksize vmtype cdrom autoinstall'.split()
209                    if fields[kw]])
210        validate = validation.Validate(cherrypy.request.login,
211                                       cherrypy.request.state,
212                                       strict=True, **kws)
213        return dict(contact=cherrypy.request.login, name=validate.name,
214                    description=validate.description, memory=validate.memory,
215                    disksize=validate.disksize, owner=validate.owner,
216                    machine_type=getattr(validate, 'vmtype', Defaults.type),
217                    cdrom=getattr(validate, 'cdrom', None),
218                    autoinstall=getattr(validate, 'autoinstall', None))
219
220    @cherrypy.expose
221    @cherrypy.tools.mako(filename="/list.mako")
222    @cherrypy.tools.require_POST()
223    def create(self, **fields):
224        """Handler for create requests."""
225        try:
226            parsed_fields = self.parseCreate(fields)
227            machine = controls.createVm(cherrypy.request.login,
228                                        cherrypy.request.state, **parsed_fields)
229        except InvalidInput, err:
230            pass
231        else:
232            err = None
233        cherrypy.request.state.clear() #Changed global state
234        d = getListDict(cherrypy.request.login, cherrypy.request.state)
235        d['err'] = err
236        if err:
237            for field, value in fields.items():
238                setattr(d['defaults'], field, value))
239        else:
240            d['new_machine'] = parsed_fields['name']
241        return d
242
243    @cherrypy.expose
244    @cherrypy.tools.mako(filename="/helloworld.mako")
245    def helloworld(self, **kwargs):
246        return {'request': cherrypy.request, 'kwargs': kwargs}
247    helloworld._cp_config['tools.require_login.on'] = False
248
249    @cherrypy.expose
250    def errortest(self):
251        """Throw an error, to test the error-tracing mechanisms."""
252        print >>sys.stderr, "look ma, it's a stderr"
253        raise RuntimeError("test of the emergency broadcast system")
254
255    class MachineView(View):
256        # This is hairy. Fix when CherryPy 3.2 is out. (rename to
257        # _cp_dispatch, and parse the argument as a list instead of
258        # string
259
260        def __getattr__(self, name):
261            try:
262                cherrypy.request.params['machine_id'] = int(name)
263                return self
264            except ValueError:
265                return None
266
267        @cherrypy.expose
268        @cherrypy.tools.mako(filename="/info.mako")
269        def info(self, machine_id):
270            """Handler for info on a single VM."""
271            machine = validation.Validate(cherrypy.request.login,
272                                          cherrypy.request.state,
273                                          machine_id=machine_id).machine
274            d = infoDict(cherrypy.request.login, cherrypy.request.state, machine)
275            checkpoint.checkpoint('Got infodict')
276            return d
277        index = info
278
279        @cherrypy.expose
280        @cherrypy.tools.mako(filename="/info.mako")
281        @cherrypy.tools.require_POST()
282        def modify(self, machine_id, **fields):
283            """Handler for modifying attributes of a machine."""
284            try:
285                modify_dict = modifyDict(cherrypy.request.login,
286                                         cherrypy.request.state,
287                                         machine_id, fields)
288            except InvalidInput, err:
289                result = None
290                machine = validation.Validate(cherrypy.request.login,
291                                              cherrypy.request.state,
292                                              machine_id=machine_id).machine
293            else:
294                machine = modify_dict['machine']
295                result = 'Success!'
296                err = None
297            info_dict = infoDict(cherrypy.request.login,
298                                 cherrypy.request.state, machine)
299            info_dict['err'] = err
300            if err:
301                for field, value in fields.items():
302                    setattr(info_dict['defaults'], field, value)
303            info_dict['result'] = result
304            return info_dict
305
306        @cherrypy.expose
307        @cherrypy.tools.mako(filename="/vnc.mako")
308        def vnc(self, machine_id):
309            """VNC applet page.
310
311            Note that due to same-domain restrictions, the applet connects to
312            the webserver, which needs to forward those requests to the xen
313            server.  The Xen server runs another proxy that (1) authenticates
314            and (2) finds the correct port for the VM.
315
316            You might want iptables like:
317
318            -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
319            --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
320            -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
321            --dport 10003 -j SNAT --to-source 18.187.7.142
322            -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
323            --dport 10003 -j ACCEPT
324
325            Remember to enable iptables!
326            echo 1 > /proc/sys/net/ipv4/ip_forward
327            """
328            machine = validation.Validate(cherrypy.request.login,
329                                          cherrypy.request.state,
330                                          machine_id=machine_id).machine
331            token = controls.vnctoken(machine)
332            host = controls.listHost(machine)
333            if host:
334                port = 10003 + [h.hostname for h in config.hosts].index(host)
335            else:
336                port = 5900 # dummy
337
338            status = controls.statusInfo(machine)
339            has_vnc = hasVnc(status)
340
341            d = dict(on=status,
342                     has_vnc=has_vnc,
343                     machine=machine,
344                     hostname=cherrypy.request.local.name,
345                     port=port,
346                     authtoken=token)
347            return d
348        @cherrypy.expose
349        @cherrypy.tools.mako(filename="/command.mako")
350        @cherrypy.tools.require_POST()
351        def command(self, command_name, machine_id, **kwargs):
352            """Handler for running commands like boot and delete on a VM."""
353            back = kwargs.get('back', None)
354            try:
355                d = controls.commandResult(cherrypy.request.login,
356                                           cherrypy.request.state,
357                                           command_name, machine_id, kwargs)
358                if d['command'] == 'Delete VM':
359                    back = 'list'
360            except InvalidInput, err:
361                if not back:
362                    raise
363                print >> sys.stderr, err
364                result = str(err)
365            else:
366                result = 'Success!'
367                if not back:
368                    return d
369            if back == 'list':
370                cherrypy.request.state.clear() #Changed global state
371                raise cherrypy.InternalRedirect('/list?result=%s'
372                                                % urllib.quote(result))
373            elif back == 'info':
374                raise cherrypy.HTTPRedirect(cherrypy.request.base
375                                            + '/machine/%d/' % machine_id,
376                                            status=303)
377            else:
378                raise InvalidInput('back', back, 'Not a known back page.')
379
380        atmulti = ajaxterm.Multiplex()
381        atsessions = {}
382        atsessions_lock = threading.Lock()
383
384        @cherrypy.expose
385        @cherrypy.tools.mako(filename="/terminal.mako")
386        def terminal(self, machine_id):
387            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
388
389            status = controls.statusInfo(machine)
390            has_vnc = hasVnc(status)
391
392            d = dict(on=status,
393                     has_vnc=has_vnc,
394                     machine=machine,
395                     hostname=cherrypy.request.local.name)
396            return d
397
398        @cherrypy.expose
399        @cherrypy.tools.require_POST()
400        @cherrypy.tools.gzip()
401        def at(self, machine_id, k=None, c=0, h=None):
402            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
403            with self.atsessions_lock:
404                if machine_id in self.atsessions:
405                    term = self.atsessions[machine_id]
406                else:
407                    print >>sys.stderr, "spawning new session for terminal to ",machine_id
408                    invirt.remctl.checkKinit(principal='daemon/'+config.web.hostname)
409                    term = self.atmulti.create(
410                        ["ssh", "-e","none", "-l", machine.name, config.console.hostname]
411                        )
412                    # Clear out old sessions when fd is reused
413                    for key in self.atsessions:
414                        if self.atsessions[key] == term:
415                            del self.atsessions[key]
416                    self.atsessions[machine_id] = term
417                if k:
418                    self.atmulti.proc_write(term,k)
419                time.sleep(0.002)
420                dump=self.atmulti.dump(term,c,h)
421                cherrypy.response.headers['Content-Type']='text/xml'
422                if isinstance(dump,str):
423                    return dump
424                else:
425                    print "Removing session for", machine_id,"because we received",repr(dump)
426                    del self.atsessions[machine_id]
427                    return '<?xml version="1.0"?><idem></idem>'
428
429    machine = MachineView()
430
431class Checkpoint:
432    def __init__(self):
433        self.start_time = time.time()
434        self.checkpoints = []
435
436    def checkpoint(self, s):
437        self.checkpoints.append((s, time.time()))
438
439    def __str__(self):
440        return ('Timing info:\n%s\n' %
441                '\n'.join(['%s: %s' % (d, t - self.start_time) for
442                           (d, t) in self.checkpoints]))
443
444checkpoint = Checkpoint()
445
446class Defaults:
447    """Class to store default values for fields."""
448    memory = 256
449    disk = 4.0
450    cdrom = ''
451    autoinstall = ''
452    name = ''
453    description = ''
454    administrator = ''
455    type = 'linux-hvm'
456
457    def __init__(self, max_memory=None, max_disk=None, **kws):
458        if max_memory is not None:
459            self.memory = min(self.memory, max_memory)
460        if max_disk is not None:
461            self.disk = min(self.disk, max_disk)
462        for key in kws:
463            setattr(self, key, kws[key])
464
465def hasVnc(status):
466    """Does the machine with a given status list support VNC?"""
467    if status is None:
468        return False
469    for l in status:
470        if l[0] == 'device' and l[1][0] == 'vfb':
471            d = dict(l[1][1:])
472            return 'location' in d
473    return False
474
475
476def getListDict(username, state):
477    """Gets the list of local variables used by list.tmpl."""
478    checkpoint.checkpoint('Starting')
479    machines = state.machines
480    checkpoint.checkpoint('Got my machines')
481    on = {}
482    has_vnc = {}
483    installing = {}
484    xmlist = state.xmlist
485    checkpoint.checkpoint('Got uptimes')
486    for m in machines:
487        if m not in xmlist:
488            has_vnc[m] = 'Off'
489            m.uptime = None
490        else:
491            m.uptime = xmlist[m]['uptime']
492            if xmlist[m]['console']:
493                has_vnc[m] = True
494            elif m.type.hvm:
495                has_vnc[m] = "WTF?"
496            else:
497                has_vnc[m] = "ParaVM"
498            if xmlist[m].get('autoinstall'):
499                installing[m] = True
500            else:
501                installing[m] = False
502    max_memory = validation.maxMemory(username, state)
503    max_disk = validation.maxDisk(username)
504    checkpoint.checkpoint('Got max mem/disk')
505    defaults = Defaults(max_memory=max_memory,
506                        max_disk=max_disk,
507                        owner=username)
508    checkpoint.checkpoint('Got defaults')
509    def sortkey(machine):
510        return (machine.owner != username, machine.owner, machine.name)
511    machines = sorted(machines, key=sortkey)
512    d = dict(user=username,
513             cant_add_vm=validation.cantAddVm(username, state),
514             max_memory=max_memory,
515             max_disk=max_disk,
516             defaults=defaults,
517             machines=machines,
518             has_vnc=has_vnc,
519             installing=installing)
520    return d
521
522def getHostname(nic):
523    """Find the hostname associated with a NIC.
524
525    XXX this should be merged with the similar logic in DNS and DHCP.
526    """
527    if nic.hostname:
528        hostname = nic.hostname
529    elif nic.machine:
530        hostname = nic.machine.name
531    else:
532        return None
533    if '.' in hostname:
534        return hostname
535    else:
536        return hostname + '.' + config.dns.domains[0]
537
538def getNicInfo(data_dict, machine):
539    """Helper function for info, get data on nics for a machine.
540
541    Modifies data_dict to include the relevant data, and returns a list
542    of (key, name) pairs to display "name: data_dict[key]" to the user.
543    """
544    data_dict['num_nics'] = len(machine.nics)
545    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
546                           ('nic%s_mac', 'NIC %s MAC Addr'),
547                           ('nic%s_ip', 'NIC %s IP'),
548                           ]
549    nic_fields = []
550    for i in range(len(machine.nics)):
551        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
552        data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
553        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
554        data_dict['nic%s_ip' % i] = machine.nics[i].ip
555    if len(machine.nics) == 1:
556        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
557    return nic_fields
558
559def getDiskInfo(data_dict, machine):
560    """Helper function for info, get data on disks for a machine.
561
562    Modifies data_dict to include the relevant data, and returns a list
563    of (key, name) pairs to display "name: data_dict[key]" to the user.
564    """
565    data_dict['num_disks'] = len(machine.disks)
566    disk_fields_template = [('%s_size', '%s size')]
567    disk_fields = []
568    for disk in machine.disks:
569        name = disk.guest_device_name
570        disk_fields.extend([(x % name, y % name) for x, y in
571                            disk_fields_template])
572        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
573    return disk_fields
574
575def modifyDict(username, state, machine_id, fields):
576    """Modify a machine as specified by CGI arguments.
577
578    Return a dict containing the machine that was modified.
579    """
580    olddisk = {}
581    session.begin()
582    try:
583        kws = dict([(kw, fields[kw]) for kw in
584         'owner admin contact name description memory vmtype disksize'.split()
585                    if fields[kw]])
586        kws['machine_id'] = machine_id
587        validate = validation.Validate(username, state, **kws)
588        machine = validate.machine
589        oldname = machine.name
590
591        if hasattr(validate, 'memory'):
592            machine.memory = validate.memory
593
594        if hasattr(validate, 'vmtype'):
595            machine.type = validate.vmtype
596
597        if hasattr(validate, 'disksize'):
598            disksize = validate.disksize
599            disk = machine.disks[0]
600            if disk.size != disksize:
601                olddisk[disk.guest_device_name] = disksize
602                disk.size = disksize
603                session.save_or_update(disk)
604
605        update_acl = False
606        if hasattr(validate, 'owner') and validate.owner != machine.owner:
607            machine.owner = validate.owner
608            update_acl = True
609        if hasattr(validate, 'name'):
610            machine.name = validate.name
611            for n in machine.nics:
612                if n.hostname == oldname:
613                    n.hostname = validate.name
614        if hasattr(validate, 'description'):
615            machine.description = validate.description
616        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
617            machine.administrator = validate.admin
618            update_acl = True
619        if hasattr(validate, 'contact'):
620            machine.contact = validate.contact
621
622        session.save_or_update(machine)
623        if update_acl:
624            cache_acls.refreshMachine(machine)
625        session.commit()
626    except:
627        session.rollback()
628        raise
629    for diskname in olddisk:
630        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
631    if hasattr(validate, 'name'):
632        controls.renameMachine(machine, oldname, validate.name)
633    return dict(machine=machine)
634
635def infoDict(username, state, machine):
636    """Get the variables used by info.tmpl."""
637    status = controls.statusInfo(machine)
638    checkpoint.checkpoint('Getting status info')
639    has_vnc = hasVnc(status)
640    if status is None:
641        main_status = dict(name=machine.name,
642                           memory=str(machine.memory))
643        uptime = None
644        cputime = None
645    else:
646        main_status = dict(status[1:])
647        main_status['host'] = controls.listHost(machine)
648        start_time = float(main_status.get('start_time', 0))
649        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
650        cpu_time_float = float(main_status.get('cpu_time', 0))
651        cputime = datetime.timedelta(seconds=int(cpu_time_float))
652    checkpoint.checkpoint('Status')
653    display_fields = [('name', 'Name'),
654                      ('description', 'Description'),
655                      ('owner', 'Owner'),
656                      ('administrator', 'Administrator'),
657                      ('contact', 'Contact'),
658                      ('type', 'Type'),
659                      'NIC_INFO',
660                      ('uptime', 'uptime'),
661                      ('cputime', 'CPU usage'),
662                      ('host', 'Hosted on'),
663                      ('memory', 'RAM'),
664                      'DISK_INFO',
665                      ('state', 'state (xen format)'),
666                      ]
667    fields = []
668    machine_info = {}
669    machine_info['name'] = machine.name
670    machine_info['description'] = machine.description
671    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
672    machine_info['owner'] = machine.owner
673    machine_info['administrator'] = machine.administrator
674    machine_info['contact'] = machine.contact
675
676    nic_fields = getNicInfo(machine_info, machine)
677    nic_point = display_fields.index('NIC_INFO')
678    display_fields = (display_fields[:nic_point] + nic_fields +
679                      display_fields[nic_point+1:])
680
681    disk_fields = getDiskInfo(machine_info, machine)
682    disk_point = display_fields.index('DISK_INFO')
683    display_fields = (display_fields[:disk_point] + disk_fields +
684                      display_fields[disk_point+1:])
685
686    main_status['memory'] += ' MiB'
687    for field, disp in display_fields:
688        if field in ('uptime', 'cputime') and locals()[field] is not None:
689            fields.append((disp, locals()[field]))
690        elif field in machine_info:
691            fields.append((disp, machine_info[field]))
692        elif field in main_status:
693            fields.append((disp, main_status[field]))
694        else:
695            pass
696            #fields.append((disp, None))
697
698    checkpoint.checkpoint('Got fields')
699
700
701    max_mem = validation.maxMemory(machine.owner, state, machine, False)
702    checkpoint.checkpoint('Got mem')
703    max_disk = validation.maxDisk(machine.owner, machine)
704    defaults = Defaults()
705    for name in 'machine_id name description administrator owner memory contact'.split():
706        if getattr(machine, name):
707            setattr(defaults, name, getattr(machine, name))
708    defaults.type = machine.type.type_id
709    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
710    checkpoint.checkpoint('Got defaults')
711    d = dict(user=username,
712             on=status is not None,
713             machine=machine,
714             defaults=defaults,
715             has_vnc=has_vnc,
716             uptime=str(uptime),
717             ram=machine.memory,
718             max_mem=max_mem,
719             max_disk=max_disk,
720             fields = fields)
721    return d
722
723def send_error_mail(subject, body):
724    import subprocess
725
726    to = config.web.errormail
727    mail = """To: %s
728From: root@%s
729Subject: %s
730
731%s
732""" % (to, config.web.hostname, subject, body)
733    p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
734                         stdin=subprocess.PIPE)
735    p.stdin.write(mail)
736    p.stdin.close()
737    p.wait()
738
739random.seed()
Note: See TracBrowser for help on using the repository browser.