source: package_branches/invirt-web/ajaxterm-rebased/code/main.py @ 2756

Last change on this file since 2756 was 2756, checked in by broder, 14 years ago

Fix race condition in ajaxterm

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