source: trunk/packages/invirt-web/code/main.py @ 2989

Last change on this file since 2989 was 2979, checked in by quentin, 15 years ago

Work around quirk of CherryPy? dispatching that causes username munging to not happen by the time dispatching happens

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