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

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

Update the cherrypy branch to use authz.afs.cells instead of just
authz.cells, to stay in sync with r2590.

(Again, this isn't really a cherry-pick, but whatever)

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