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

Last change on this file since 2532 was 2530, checked in by price, 15 years ago

fix a syntax error from my r2526

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