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

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

Document MachineView?.getattr

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