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

Last change on this file since 2730 was 2730, checked in by broder, 14 years ago

Setup hosting for static resources in the InvirtWeb? and
InvirtUnauthWeb? classes, instead of in the fcgi configuration.

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