source: package_branches/invirt-web/cherrypy/code/main.py @ 2652

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

Setup hosting for static resources in the InvirtWeb? and
InvirtUnauthWeb? classes, instead of in the fcgi configuration.

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