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

Last change on this file since 2524 was 2524, checked in by price, 14 years ago

tighten a bit of code

  • Property svn:executable set to *
File size: 27.8 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.get(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split() if fields.get(kw)])
208        validate = validation.Validate(cherrypy.request.login, cherrypy.request.state, strict=True, **kws)
209        return dict(contact=cherrypy.request.login, name=validate.name, description=validate.description, memory=validate.memory,
210                    disksize=validate.disksize, owner=validate.owner, machine_type=getattr(validate, 'vmtype', Defaults.type),
211                    cdrom=getattr(validate, 'cdrom', None),
212                    autoinstall=getattr(validate, 'autoinstall', None))
213
214    @cherrypy.expose
215    @cherrypy.tools.mako(filename="/list.mako")
216    @cherrypy.tools.require_POST()
217    def create(self, **fields):
218        """Handler for create requests."""
219        try:
220            parsed_fields = self.parseCreate(fields)
221            machine = controls.createVm(cherrypy.request.login, cherrypy.request.state, **parsed_fields)
222        except InvalidInput, err:
223            pass
224        else:
225            err = None
226        cherrypy.request.state.clear() #Changed global state
227        d = getListDict(cherrypy.request.login, cherrypy.request.state)
228        d['err'] = err
229        if err:
230            for field in fields.keys():
231                setattr(d['defaults'], field, fields.get(field))
232        else:
233            d['new_machine'] = parsed_fields['name']
234        return d
235
236    @cherrypy.expose
237    @cherrypy.tools.mako(filename="/helloworld.mako")
238    def helloworld(self, **kwargs):
239        return {'request': cherrypy.request, 'kwargs': kwargs}
240    helloworld._cp_config['tools.require_login.on'] = False
241
242    @cherrypy.expose
243    def errortest(self):
244        """Throw an error, to test the error-tracing mechanisms."""
245        print >>sys.stderr, "look ma, it's a stderr"
246        raise RuntimeError("test of the emergency broadcast system")
247
248    class MachineView(View):
249        # This is hairy. Fix when CherryPy 3.2 is out. (rename to
250        # _cp_dispatch, and parse the argument as a list instead of
251        # string
252
253        def __getattr__(self, name):
254            try:
255                machine_id = int(name)
256                cherrypy.request.params['machine_id'] = machine_id
257                return self
258            except ValueError:
259                return None
260
261        @cherrypy.expose
262        @cherrypy.tools.mako(filename="/info.mako")
263        def info(self, machine_id):
264            """Handler for info on a single VM."""
265            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
266            d = infoDict(cherrypy.request.login, cherrypy.request.state, machine)
267            checkpoint.checkpoint('Got infodict')
268            return d
269        index = info
270
271        @cherrypy.expose
272        @cherrypy.tools.mako(filename="/info.mako")
273        @cherrypy.tools.require_POST()
274        def modify(self, machine_id, **fields):
275            """Handler for modifying attributes of a machine."""
276            try:
277                modify_dict = modifyDict(cherrypy.request.login, cherrypy.request.state, machine_id, fields)
278            except InvalidInput, err:
279                result = None
280                machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
281            else:
282                machine = modify_dict['machine']
283                result = 'Success!'
284                err = None
285            info_dict = infoDict(cherrypy.request.login, cherrypy.request.state, machine)
286            info_dict['err'] = err
287            if err:
288                for field in fields.keys():
289                    setattr(info_dict['defaults'], field, fields.get(field))
290            info_dict['result'] = result
291            return info_dict
292
293        @cherrypy.expose
294        @cherrypy.tools.mako(filename="/vnc.mako")
295        def vnc(self, machine_id):
296            """VNC applet page.
297
298            Note that due to same-domain restrictions, the applet connects to
299            the webserver, which needs to forward those requests to the xen
300            server.  The Xen server runs another proxy that (1) authenticates
301            and (2) finds the correct port for the VM.
302
303            You might want iptables like:
304
305            -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
306            --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
307            -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
308            --dport 10003 -j SNAT --to-source 18.187.7.142
309            -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
310            --dport 10003 -j ACCEPT
311
312            Remember to enable iptables!
313            echo 1 > /proc/sys/net/ipv4/ip_forward
314            """
315            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
316
317            token = controls.vnctoken(machine)
318            host = controls.listHost(machine)
319            if host:
320                port = 10003 + [h.hostname for h in config.hosts].index(host)
321            else:
322                port = 5900 # dummy
323
324            status = controls.statusInfo(machine)
325            has_vnc = hasVnc(status)
326
327            d = dict(on=status,
328                     has_vnc=has_vnc,
329                     machine=machine,
330                     hostname=cherrypy.request.local.name,
331                     port=port,
332                     authtoken=token)
333            return d
334        @cherrypy.expose
335        @cherrypy.tools.mako(filename="/command.mako")
336        @cherrypy.tools.require_POST()
337        def command(self, command_name, machine_id, **kwargs):
338            """Handler for running commands like boot and delete on a VM."""
339            back = kwargs.get('back', None)
340            try:
341                d = controls.commandResult(cherrypy.request.login, cherrypy.request.state, command_name, machine_id, kwargs)
342                if d['command'] == 'Delete VM':
343                    back = 'list'
344            except InvalidInput, err:
345                if not back:
346                    raise
347                print >> sys.stderr, err
348                result = str(err)
349            else:
350                result = 'Success!'
351                if not back:
352                    return d
353            if back == 'list':
354                cherrypy.request.state.clear() #Changed global state
355                raise cherrypy.InternalRedirect('/list?result=%s' % urllib.quote(result))
356            elif back == 'info':
357                raise cherrypy.HTTPRedirect(cherrypy.request.base + '/machine/%d/' % machine_id, status=303)
358            else:
359                raise InvalidInput('back', back, 'Not a known back page.')
360
361        atmulti = ajaxterm.Multiplex()
362        atsessions = {}
363        atsessions_lock = threading.Lock()
364
365        @cherrypy.expose
366        @cherrypy.tools.mako(filename="/terminal.mako")
367        def terminal(self, machine_id):
368            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
369
370            status = controls.statusInfo(machine)
371            has_vnc = hasVnc(status)
372
373            d = dict(on=status,
374                     has_vnc=has_vnc,
375                     machine=machine,
376                     hostname=cherrypy.request.local.name)
377            return d
378
379        @cherrypy.expose
380        @cherrypy.tools.require_POST()
381        @cherrypy.tools.gzip()
382        def at(self, machine_id, k=None, c=0, h=None):
383            machine = validation.Validate(cherrypy.request.login, cherrypy.request.state, machine_id=machine_id).machine
384            with self.atsessions_lock:
385                if machine_id in self.atsessions:
386                    term = self.atsessions[machine_id]
387                else:
388                    print >>sys.stderr, "spawning new session for terminal to ",machine_id
389                    invirt.remctl.checkKinit(principal='daemon/'+config.web.hostname)
390                    term = self.atmulti.create(
391                        ["ssh", "-e","none", "-l", machine.name, config.console.hostname]
392                        )
393                    # Clear out old sessions when fd is reused
394                    for key in self.atsessions:
395                        if self.atsessions[key] == term:
396                            del self.atsessions[key]
397                    self.atsessions[machine_id] = term
398                if k:
399                    self.atmulti.proc_write(term,k)
400                time.sleep(0.002)
401                dump=self.atmulti.dump(term,c,h)
402                cherrypy.response.headers['Content-Type']='text/xml'
403                if isinstance(dump,str):
404                    return dump
405                else:
406                    print "Removing session for", machine_id,"because we received",repr(dump)
407                    del self.atsessions[machine_id]
408                    return '<?xml version="1.0"?><idem></idem>'
409
410    machine = MachineView()
411
412class Checkpoint:
413    def __init__(self):
414        self.start_time = time.time()
415        self.checkpoints = []
416
417    def checkpoint(self, s):
418        self.checkpoints.append((s, time.time()))
419
420    def __str__(self):
421        return ('Timing info:\n%s\n' %
422                '\n'.join(['%s: %s' % (d, t - self.start_time) for
423                           (d, t) in self.checkpoints]))
424
425checkpoint = Checkpoint()
426
427class Defaults:
428    """Class to store default values for fields."""
429    memory = 256
430    disk = 4.0
431    cdrom = ''
432    autoinstall = ''
433    name = ''
434    description = ''
435    administrator = ''
436    type = 'linux-hvm'
437
438    def __init__(self, max_memory=None, max_disk=None, **kws):
439        if max_memory is not None:
440            self.memory = min(self.memory, max_memory)
441        if max_disk is not None:
442            self.disk = min(self.disk, max_disk)
443        for key in kws:
444            setattr(self, key, kws[key])
445
446def hasVnc(status):
447    """Does the machine with a given status list support VNC?"""
448    if status is None:
449        return False
450    for l in status:
451        if l[0] == 'device' and l[1][0] == 'vfb':
452            d = dict(l[1][1:])
453            return 'location' in d
454    return False
455
456
457def getListDict(username, state):
458    """Gets the list of local variables used by list.tmpl."""
459    checkpoint.checkpoint('Starting')
460    machines = state.machines
461    checkpoint.checkpoint('Got my machines')
462    on = {}
463    has_vnc = {}
464    installing = {}
465    xmlist = state.xmlist
466    checkpoint.checkpoint('Got uptimes')
467    for m in machines:
468        if m not in xmlist:
469            has_vnc[m] = 'Off'
470            m.uptime = None
471        else:
472            m.uptime = xmlist[m]['uptime']
473            if xmlist[m]['console']:
474                has_vnc[m] = True
475            elif m.type.hvm:
476                has_vnc[m] = "WTF?"
477            else:
478                has_vnc[m] = "ParaVM"
479            if xmlist[m].get('autoinstall'):
480                installing[m] = True
481            else:
482                installing[m] = False
483    max_memory = validation.maxMemory(username, state)
484    max_disk = validation.maxDisk(username)
485    checkpoint.checkpoint('Got max mem/disk')
486    defaults = Defaults(max_memory=max_memory,
487                        max_disk=max_disk,
488                        owner=username)
489    checkpoint.checkpoint('Got defaults')
490    def sortkey(machine):
491        return (machine.owner != username, machine.owner, machine.name)
492    machines = sorted(machines, key=sortkey)
493    d = dict(user=username,
494             cant_add_vm=validation.cantAddVm(username, state),
495             max_memory=max_memory,
496             max_disk=max_disk,
497             defaults=defaults,
498             machines=machines,
499             has_vnc=has_vnc,
500             installing=installing)
501    return d
502
503def getHostname(nic):
504    """Find the hostname associated with a NIC.
505
506    XXX this should be merged with the similar logic in DNS and DHCP.
507    """
508    if nic.hostname:
509        hostname = nic.hostname
510    elif nic.machine:
511        hostname = nic.machine.name
512    else:
513        return None
514    if '.' in hostname:
515        return hostname
516    else:
517        return hostname + '.' + config.dns.domains[0]
518
519def getNicInfo(data_dict, machine):
520    """Helper function for info, get data on nics for a machine.
521
522    Modifies data_dict to include the relevant data, and returns a list
523    of (key, name) pairs to display "name: data_dict[key]" to the user.
524    """
525    data_dict['num_nics'] = len(machine.nics)
526    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
527                           ('nic%s_mac', 'NIC %s MAC Addr'),
528                           ('nic%s_ip', 'NIC %s IP'),
529                           ]
530    nic_fields = []
531    for i in range(len(machine.nics)):
532        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
533        data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
534        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
535        data_dict['nic%s_ip' % i] = machine.nics[i].ip
536    if len(machine.nics) == 1:
537        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
538    return nic_fields
539
540def getDiskInfo(data_dict, machine):
541    """Helper function for info, get data on disks for a machine.
542
543    Modifies data_dict to include the relevant data, and returns a list
544    of (key, name) pairs to display "name: data_dict[key]" to the user.
545    """
546    data_dict['num_disks'] = len(machine.disks)
547    disk_fields_template = [('%s_size', '%s size')]
548    disk_fields = []
549    for disk in machine.disks:
550        name = disk.guest_device_name
551        disk_fields.extend([(x % name, y % name) for x, y in
552                            disk_fields_template])
553        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
554    return disk_fields
555
556def modifyDict(username, state, machine_id, fields):
557    """Modify a machine as specified by CGI arguments.
558
559    Return a dict containing the machine that was modified.
560    """
561    olddisk = {}
562    session.begin()
563    try:
564        kws = dict([(kw, fields.get(kw)) for kw in 'owner admin contact name description memory vmtype disksize'.split() if fields.get(kw)])
565        kws['machine_id'] = machine_id
566        validate = validation.Validate(username, state, **kws)
567        machine = validate.machine
568        oldname = machine.name
569
570        if hasattr(validate, 'memory'):
571            machine.memory = validate.memory
572
573        if hasattr(validate, 'vmtype'):
574            machine.type = validate.vmtype
575
576        if hasattr(validate, 'disksize'):
577            disksize = validate.disksize
578            disk = machine.disks[0]
579            if disk.size != disksize:
580                olddisk[disk.guest_device_name] = disksize
581                disk.size = disksize
582                session.save_or_update(disk)
583
584        update_acl = False
585        if hasattr(validate, 'owner') and validate.owner != machine.owner:
586            machine.owner = validate.owner
587            update_acl = True
588        if hasattr(validate, 'name'):
589            machine.name = validate.name
590            for n in machine.nics:
591                if n.hostname == oldname:
592                    n.hostname = validate.name
593        if hasattr(validate, 'description'):
594            machine.description = validate.description
595        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
596            machine.administrator = validate.admin
597            update_acl = True
598        if hasattr(validate, 'contact'):
599            machine.contact = validate.contact
600
601        session.save_or_update(machine)
602        if update_acl:
603            cache_acls.refreshMachine(machine)
604        session.commit()
605    except:
606        session.rollback()
607        raise
608    for diskname in olddisk:
609        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
610    if hasattr(validate, 'name'):
611        controls.renameMachine(machine, oldname, validate.name)
612    return dict(machine=machine)
613
614def infoDict(username, state, machine):
615    """Get the variables used by info.tmpl."""
616    status = controls.statusInfo(machine)
617    checkpoint.checkpoint('Getting status info')
618    has_vnc = hasVnc(status)
619    if status is None:
620        main_status = dict(name=machine.name,
621                           memory=str(machine.memory))
622        uptime = None
623        cputime = None
624    else:
625        main_status = dict(status[1:])
626        main_status['host'] = controls.listHost(machine)
627        start_time = float(main_status.get('start_time', 0))
628        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
629        cpu_time_float = float(main_status.get('cpu_time', 0))
630        cputime = datetime.timedelta(seconds=int(cpu_time_float))
631    checkpoint.checkpoint('Status')
632    display_fields = [('name', 'Name'),
633                      ('description', 'Description'),
634                      ('owner', 'Owner'),
635                      ('administrator', 'Administrator'),
636                      ('contact', 'Contact'),
637                      ('type', 'Type'),
638                      'NIC_INFO',
639                      ('uptime', 'uptime'),
640                      ('cputime', 'CPU usage'),
641                      ('host', 'Hosted on'),
642                      ('memory', 'RAM'),
643                      'DISK_INFO',
644                      ('state', 'state (xen format)'),
645                      ]
646    fields = []
647    machine_info = {}
648    machine_info['name'] = machine.name
649    machine_info['description'] = machine.description
650    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
651    machine_info['owner'] = machine.owner
652    machine_info['administrator'] = machine.administrator
653    machine_info['contact'] = machine.contact
654
655    nic_fields = getNicInfo(machine_info, machine)
656    nic_point = display_fields.index('NIC_INFO')
657    display_fields = (display_fields[:nic_point] + nic_fields +
658                      display_fields[nic_point+1:])
659
660    disk_fields = getDiskInfo(machine_info, machine)
661    disk_point = display_fields.index('DISK_INFO')
662    display_fields = (display_fields[:disk_point] + disk_fields +
663                      display_fields[disk_point+1:])
664
665    main_status['memory'] += ' MiB'
666    for field, disp in display_fields:
667        if field in ('uptime', 'cputime') and locals()[field] is not None:
668            fields.append((disp, locals()[field]))
669        elif field in machine_info:
670            fields.append((disp, machine_info[field]))
671        elif field in main_status:
672            fields.append((disp, main_status[field]))
673        else:
674            pass
675            #fields.append((disp, None))
676
677    checkpoint.checkpoint('Got fields')
678
679
680    max_mem = validation.maxMemory(machine.owner, state, machine, False)
681    checkpoint.checkpoint('Got mem')
682    max_disk = validation.maxDisk(machine.owner, machine)
683    defaults = Defaults()
684    for name in 'machine_id name description administrator owner memory contact'.split():
685        if getattr(machine, name):
686            setattr(defaults, name, getattr(machine, name))
687    defaults.type = machine.type.type_id
688    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
689    checkpoint.checkpoint('Got defaults')
690    d = dict(user=username,
691             on=status is not None,
692             machine=machine,
693             defaults=defaults,
694             has_vnc=has_vnc,
695             uptime=str(uptime),
696             ram=machine.memory,
697             max_mem=max_mem,
698             max_disk=max_disk,
699             fields = fields)
700    return d
701
702def send_error_mail(subject, body):
703    import subprocess
704
705    to = config.web.errormail
706    mail = """To: %s
707From: root@%s
708Subject: %s
709
710%s
711""" % (to, config.web.hostname, subject, body)
712    p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
713                         stdin=subprocess.PIPE)
714    p.stdin.write(mail)
715    p.stdin.close()
716    p.wait()
717
718random.seed()
Note: See TracBrowser for help on using the repository browser.