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

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

Clear stale fds out of cache in ajaxterm

  • Property svn:executable set to *
File size: 27.9 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.atmulti.create(
423                        ["ssh", "-e","none", "-l", machine.name, config.console.hostname]
424                        )
425                    # Clear out old sessions when fd is reused
426                    for key in self.atsessions:
427                        if self.atsessions[key] == term:
428                            del self.atsessions[key]
429                    self.atsessions[machine_id] = term
430                if k:
431                    self.atmulti.proc_write(term,k)
432                time.sleep(0.002)
433                dump=self.atmulti.dump(term,c,int(force))
434                cherrypy.response.headers['Content-Type']='text/xml'
435                if isinstance(dump,str):
436                    return dump
437                else:
438                    print "Removing session for", machine_id
439                    del self.atsessions[machine_id]
440                    return '<?xml version="1.0"?><idem></idem>'
441
442    machine = MachineView()
443
444
445class Defaults:
446    """Class to store default values for fields."""
447    memory = 256
448    disk = 4.0
449    cdrom = ''
450    autoinstall = ''
451    name = ''
452    description = ''
453    administrator = ''
454    type = 'linux-hvm'
455
456    def __init__(self, max_memory=None, max_disk=None, **kws):
457        if max_memory is not None:
458            self.memory = min(self.memory, max_memory)
459        if max_disk is not None:
460            self.disk = min(self.disk, max_disk)
461        for key in kws:
462            setattr(self, key, kws[key])
463
464def hasVnc(status):
465    """Does the machine with a given status list support VNC?"""
466    if status is None:
467        return False
468    for l in status:
469        if l[0] == 'device' and l[1][0] == 'vfb':
470            d = dict(l[1][1:])
471            return 'location' in d
472    return False
473
474
475def getListDict(username, state):
476    """Gets the list of local variables used by list.tmpl."""
477    machines = state.machines
478    on = {}
479    has_vnc = {}
480    installing = {}
481    xmlist = state.xmlist
482    for m in machines:
483        if m not in xmlist:
484            has_vnc[m] = 'Off'
485            m.uptime = None
486        else:
487            m.uptime = xmlist[m]['uptime']
488            installing[m] = bool(xmlist[m].get('autoinstall'))
489            if xmlist[m]['console']:
490                has_vnc[m] = True
491            elif m.type.hvm:
492                has_vnc[m] = "WTF?"
493            else:
494                has_vnc[m] = "ParaVM"
495    max_memory = validation.maxMemory(username, state)
496    max_disk = validation.maxDisk(username)
497    defaults = Defaults(max_memory=max_memory,
498                        max_disk=max_disk,
499                        owner=username)
500    def sortkey(machine):
501        return (machine.owner != username, machine.owner, machine.name)
502    machines = sorted(machines, key=sortkey)
503    d = dict(user=username,
504             cant_add_vm=validation.cantAddVm(username, state),
505             max_memory=max_memory,
506             max_disk=max_disk,
507             defaults=defaults,
508             machines=machines,
509             has_vnc=has_vnc,
510             installing=installing)
511    return d
512
513def getHostname(nic):
514    """Find the hostname associated with a NIC.
515
516    XXX this should be merged with the similar logic in DNS and DHCP.
517    """
518    if nic.hostname:
519        hostname = nic.hostname
520    elif nic.machine:
521        hostname = nic.machine.name
522    else:
523        return None
524    if '.' in hostname:
525        return hostname
526    else:
527        return hostname + '.' + config.dns.domains[0]
528
529def getNicInfo(data_dict, machine):
530    """Helper function for info, get data on nics for a machine.
531
532    Modifies data_dict to include the relevant data, and returns a list
533    of (key, name) pairs to display "name: data_dict[key]" to the user.
534    """
535    data_dict['num_nics'] = len(machine.nics)
536    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
537                           ('nic%s_mac', 'NIC %s MAC Addr'),
538                           ('nic%s_ip', 'NIC %s IP'),
539                           ]
540    nic_fields = []
541    for i in range(len(machine.nics)):
542        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
543        data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
544        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
545        data_dict['nic%s_ip' % i] = machine.nics[i].ip
546    if len(machine.nics) == 1:
547        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
548    return nic_fields
549
550def getDiskInfo(data_dict, machine):
551    """Helper function for info, get data on disks for a machine.
552
553    Modifies data_dict to include the relevant data, and returns a list
554    of (key, name) pairs to display "name: data_dict[key]" to the user.
555    """
556    data_dict['num_disks'] = len(machine.disks)
557    disk_fields_template = [('%s_size', '%s size')]
558    disk_fields = []
559    for disk in machine.disks:
560        name = disk.guest_device_name
561        disk_fields.extend([(x % name, y % name) for x, y in
562                            disk_fields_template])
563        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
564    return disk_fields
565
566def modifyDict(username, state, machine_id, fields):
567    """Modify a machine as specified by CGI arguments.
568
569    Return a dict containing the machine that was modified.
570    """
571    olddisk = {}
572    session.begin()
573    try:
574        kws = dict([(kw, fields[kw]) for kw in
575         'owner admin contact name description memory vmtype disksize'.split()
576                    if fields[kw]])
577        kws['machine_id'] = machine_id
578        validate = validation.Validate(username, state, **kws)
579        machine = validate.machine
580        oldname = machine.name
581
582        if hasattr(validate, 'memory'):
583            machine.memory = validate.memory
584
585        if hasattr(validate, 'vmtype'):
586            machine.type = validate.vmtype
587
588        if hasattr(validate, 'disksize'):
589            disksize = validate.disksize
590            disk = machine.disks[0]
591            if disk.size != disksize:
592                olddisk[disk.guest_device_name] = disksize
593                disk.size = disksize
594                session.save_or_update(disk)
595
596        update_acl = False
597        if hasattr(validate, 'owner') and validate.owner != machine.owner:
598            machine.owner = validate.owner
599            update_acl = True
600        if hasattr(validate, 'name'):
601            machine.name = validate.name
602            for n in machine.nics:
603                if n.hostname == oldname:
604                    n.hostname = validate.name
605        if hasattr(validate, 'description'):
606            machine.description = validate.description
607        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
608            machine.administrator = validate.admin
609            update_acl = True
610        if hasattr(validate, 'contact'):
611            machine.contact = validate.contact
612
613        session.save_or_update(machine)
614        if update_acl:
615            cache_acls.refreshMachine(machine)
616        session.commit()
617    except:
618        session.rollback()
619        raise
620    for diskname in olddisk:
621        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
622    if hasattr(validate, 'name'):
623        controls.renameMachine(machine, oldname, validate.name)
624    return dict(machine=machine)
625
626def infoDict(username, state, machine):
627    """Get the variables used by info.tmpl."""
628    status = controls.statusInfo(machine)
629    has_vnc = hasVnc(status)
630    if status is None:
631        main_status = dict(name=machine.name,
632                           memory=str(machine.memory))
633        uptime = None
634        cputime = None
635    else:
636        main_status = dict(status[1:])
637        main_status['host'] = controls.listHost(machine)
638        start_time = float(main_status.get('start_time', 0))
639        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
640        cpu_time_float = float(main_status.get('cpu_time', 0))
641        cputime = datetime.timedelta(seconds=int(cpu_time_float))
642    display_fields = [('name', 'Name'),
643                      ('description', 'Description'),
644                      ('owner', 'Owner'),
645                      ('administrator', 'Administrator'),
646                      ('contact', 'Contact'),
647                      ('type', 'Type'),
648                      'NIC_INFO',
649                      ('uptime', 'uptime'),
650                      ('cputime', 'CPU usage'),
651                      ('host', 'Hosted on'),
652                      ('memory', 'RAM'),
653                      'DISK_INFO',
654                      ('state', 'state (xen format)'),
655                      ]
656    fields = []
657    machine_info = {}
658    machine_info['name'] = machine.name
659    machine_info['description'] = machine.description
660    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
661    machine_info['owner'] = machine.owner
662    machine_info['administrator'] = machine.administrator
663    machine_info['contact'] = machine.contact
664
665    nic_fields = getNicInfo(machine_info, machine)
666    nic_point = display_fields.index('NIC_INFO')
667    display_fields = (display_fields[:nic_point] + nic_fields +
668                      display_fields[nic_point+1:])
669
670    disk_fields = getDiskInfo(machine_info, machine)
671    disk_point = display_fields.index('DISK_INFO')
672    display_fields = (display_fields[:disk_point] + disk_fields +
673                      display_fields[disk_point+1:])
674
675    main_status['memory'] += ' MiB'
676    for field, disp in display_fields:
677        if field in ('uptime', 'cputime') and locals()[field] is not None:
678            fields.append((disp, locals()[field]))
679        elif field in machine_info:
680            fields.append((disp, machine_info[field]))
681        elif field in main_status:
682            fields.append((disp, main_status[field]))
683        else:
684            pass
685            #fields.append((disp, None))
686
687    max_mem = validation.maxMemory(machine.owner, state, machine, False)
688    max_disk = validation.maxDisk(machine.owner, machine)
689    defaults = Defaults()
690    for name in 'machine_id name description administrator owner memory contact'.split():
691        if getattr(machine, name):
692            setattr(defaults, name, getattr(machine, name))
693    defaults.type = machine.type.type_id
694    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
695    d = dict(user=username,
696             on=status is not None,
697             machine=machine,
698             defaults=defaults,
699             has_vnc=has_vnc,
700             uptime=str(uptime),
701             ram=machine.memory,
702             max_mem=max_mem,
703             max_disk=max_disk,
704             fields = fields)
705    return d
706
707def send_error_mail(subject, body):
708    import subprocess
709
710    to = config.web.errormail
711    mail = """To: %s
712From: root@%s
713Subject: %s
714
715%s
716""" % (to, config.web.hostname, subject, body)
717    p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
718                         stdin=subprocess.PIPE)
719    p.stdin.write(mail)
720    p.stdin.close()
721    p.wait()
722
723random.seed() #sigh
Note: See TracBrowser for help on using the repository browser.