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

Last change on this file since 2751 was 2751, checked in by broder, 15 years ago

Add terminal page

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