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

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

cut a leftover comment, fix a bit of spacing

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