source: package_branches/invirt-web/ajaxterm-rebased/code/main.py @ 2939

Last change on this file since 2939 was 2764, checked in by broder, 15 years ago

Ensure Kerberos tickets get passed to the ssh that ajaxterm spawns

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