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

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

Full error handling

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