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

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

Compress ajaxterm redraws with gzip, to reduce latency

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