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

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

Full error handling

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