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

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

Clear stale fds out of cache in ajaxterm

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