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

Last change on this file since 2643 was 2642, checked in by price, 15 years ago

main.py: small Python style improvement

  • Property svn:executable set to *
File size: 28.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.afs.cells[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            installing[m] = bool(xmlist[m].get('autoinstall'))
500            if xmlist[m]['console']:
501                has_vnc[m] = True
502            elif m.type.hvm:
503                has_vnc[m] = "WTF?"
504            else:
505                has_vnc[m] = "ParaVM"
506    max_memory = validation.maxMemory(username, state)
507    max_disk = validation.maxDisk(username)
508    checkpoint.checkpoint('Got max mem/disk')
509    defaults = Defaults(max_memory=max_memory,
510                        max_disk=max_disk,
511                        owner=username)
512    checkpoint.checkpoint('Got defaults')
513    def sortkey(machine):
514        return (machine.owner != username, machine.owner, machine.name)
515    machines = sorted(machines, key=sortkey)
516    d = dict(user=username,
517             cant_add_vm=validation.cantAddVm(username, state),
518             max_memory=max_memory,
519             max_disk=max_disk,
520             defaults=defaults,
521             machines=machines,
522             has_vnc=has_vnc,
523             installing=installing)
524    return d
525
526def getHostname(nic):
527    """Find the hostname associated with a NIC.
528
529    XXX this should be merged with the similar logic in DNS and DHCP.
530    """
531    if nic.hostname:
532        hostname = nic.hostname
533    elif nic.machine:
534        hostname = nic.machine.name
535    else:
536        return None
537    if '.' in hostname:
538        return hostname
539    else:
540        return hostname + '.' + config.dns.domains[0]
541
542def getNicInfo(data_dict, machine):
543    """Helper function for info, get data on nics for a machine.
544
545    Modifies data_dict to include the relevant data, and returns a list
546    of (key, name) pairs to display "name: data_dict[key]" to the user.
547    """
548    data_dict['num_nics'] = len(machine.nics)
549    nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
550                           ('nic%s_mac', 'NIC %s MAC Addr'),
551                           ('nic%s_ip', 'NIC %s IP'),
552                           ]
553    nic_fields = []
554    for i in range(len(machine.nics)):
555        nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
556        data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
557        data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
558        data_dict['nic%s_ip' % i] = machine.nics[i].ip
559    if len(machine.nics) == 1:
560        nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
561    return nic_fields
562
563def getDiskInfo(data_dict, machine):
564    """Helper function for info, get data on disks for a machine.
565
566    Modifies data_dict to include the relevant data, and returns a list
567    of (key, name) pairs to display "name: data_dict[key]" to the user.
568    """
569    data_dict['num_disks'] = len(machine.disks)
570    disk_fields_template = [('%s_size', '%s size')]
571    disk_fields = []
572    for disk in machine.disks:
573        name = disk.guest_device_name
574        disk_fields.extend([(x % name, y % name) for x, y in
575                            disk_fields_template])
576        data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
577    return disk_fields
578
579def modifyDict(username, state, machine_id, fields):
580    """Modify a machine as specified by CGI arguments.
581
582    Return a dict containing the machine that was modified.
583    """
584    olddisk = {}
585    session.begin()
586    try:
587        kws = dict([(kw, fields[kw]) for kw in
588         'owner admin contact name description memory vmtype disksize'.split()
589                    if fields[kw]])
590        kws['machine_id'] = machine_id
591        validate = validation.Validate(username, state, **kws)
592        machine = validate.machine
593        oldname = machine.name
594
595        if hasattr(validate, 'memory'):
596            machine.memory = validate.memory
597
598        if hasattr(validate, 'vmtype'):
599            machine.type = validate.vmtype
600
601        if hasattr(validate, 'disksize'):
602            disksize = validate.disksize
603            disk = machine.disks[0]
604            if disk.size != disksize:
605                olddisk[disk.guest_device_name] = disksize
606                disk.size = disksize
607                session.save_or_update(disk)
608
609        update_acl = False
610        if hasattr(validate, 'owner') and validate.owner != machine.owner:
611            machine.owner = validate.owner
612            update_acl = True
613        if hasattr(validate, 'name'):
614            machine.name = validate.name
615            for n in machine.nics:
616                if n.hostname == oldname:
617                    n.hostname = validate.name
618        if hasattr(validate, 'description'):
619            machine.description = validate.description
620        if hasattr(validate, 'admin') and validate.admin != machine.administrator:
621            machine.administrator = validate.admin
622            update_acl = True
623        if hasattr(validate, 'contact'):
624            machine.contact = validate.contact
625
626        session.save_or_update(machine)
627        if update_acl:
628            cache_acls.refreshMachine(machine)
629        session.commit()
630    except:
631        session.rollback()
632        raise
633    for diskname in olddisk:
634        controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
635    if hasattr(validate, 'name'):
636        controls.renameMachine(machine, oldname, validate.name)
637    return dict(machine=machine)
638
639def infoDict(username, state, machine):
640    """Get the variables used by info.tmpl."""
641    status = controls.statusInfo(machine)
642    checkpoint.checkpoint('Getting status info')
643    has_vnc = hasVnc(status)
644    if status is None:
645        main_status = dict(name=machine.name,
646                           memory=str(machine.memory))
647        uptime = None
648        cputime = None
649    else:
650        main_status = dict(status[1:])
651        main_status['host'] = controls.listHost(machine)
652        start_time = float(main_status.get('start_time', 0))
653        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
654        cpu_time_float = float(main_status.get('cpu_time', 0))
655        cputime = datetime.timedelta(seconds=int(cpu_time_float))
656    checkpoint.checkpoint('Status')
657    display_fields = [('name', 'Name'),
658                      ('description', 'Description'),
659                      ('owner', 'Owner'),
660                      ('administrator', 'Administrator'),
661                      ('contact', 'Contact'),
662                      ('type', 'Type'),
663                      'NIC_INFO',
664                      ('uptime', 'uptime'),
665                      ('cputime', 'CPU usage'),
666                      ('host', 'Hosted on'),
667                      ('memory', 'RAM'),
668                      'DISK_INFO',
669                      ('state', 'state (xen format)'),
670                      ]
671    fields = []
672    machine_info = {}
673    machine_info['name'] = machine.name
674    machine_info['description'] = machine.description
675    machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
676    machine_info['owner'] = machine.owner
677    machine_info['administrator'] = machine.administrator
678    machine_info['contact'] = machine.contact
679
680    nic_fields = getNicInfo(machine_info, machine)
681    nic_point = display_fields.index('NIC_INFO')
682    display_fields = (display_fields[:nic_point] + nic_fields +
683                      display_fields[nic_point+1:])
684
685    disk_fields = getDiskInfo(machine_info, machine)
686    disk_point = display_fields.index('DISK_INFO')
687    display_fields = (display_fields[:disk_point] + disk_fields +
688                      display_fields[disk_point+1:])
689
690    main_status['memory'] += ' MiB'
691    for field, disp in display_fields:
692        if field in ('uptime', 'cputime') and locals()[field] is not None:
693            fields.append((disp, locals()[field]))
694        elif field in machine_info:
695            fields.append((disp, machine_info[field]))
696        elif field in main_status:
697            fields.append((disp, main_status[field]))
698        else:
699            pass
700            #fields.append((disp, None))
701
702    checkpoint.checkpoint('Got fields')
703
704
705    max_mem = validation.maxMemory(machine.owner, state, machine, False)
706    checkpoint.checkpoint('Got mem')
707    max_disk = validation.maxDisk(machine.owner, machine)
708    defaults = Defaults()
709    for name in 'machine_id name description administrator owner memory contact'.split():
710        if getattr(machine, name):
711            setattr(defaults, name, getattr(machine, name))
712    defaults.type = machine.type.type_id
713    defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
714    checkpoint.checkpoint('Got defaults')
715    d = dict(user=username,
716             on=status is not None,
717             machine=machine,
718             defaults=defaults,
719             has_vnc=has_vnc,
720             uptime=str(uptime),
721             ram=machine.memory,
722             max_mem=max_mem,
723             max_disk=max_disk,
724             fields = fields)
725    return d
726
727def send_error_mail(subject, body):
728    import subprocess
729
730    to = config.web.errormail
731    mail = """To: %s
732From: root@%s
733Subject: %s
734
735%s
736""" % (to, config.web.hostname, subject, body)
737    p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
738                         stdin=subprocess.PIPE)
739    p.stdin.write(mail)
740    p.stdin.close()
741    p.wait()
742
743random.seed() #sigh
Note: See TracBrowser for help on using the repository browser.