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

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

ajaxterm!

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