Index: /package_branches/invirt-web/ajaxterm-rebased/code/Makefile
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/Makefile (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/Makefile (revision 2746)
@@ -0,0 +1,8 @@
+all: kill chmod
+
+chmod:
+ chgrp -R invirt . 2>/dev/null || true
+ chmod -R g+w . 2>/dev/null || true
+
+kill:
+ -pkill main.fcgi
Index: /package_branches/invirt-web/ajaxterm-rebased/code/auth.fcgi
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/auth.fcgi (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/auth.fcgi (revision 2746)
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec ./invirt.fcgi auth "$@"
Index: /package_branches/invirt-web/ajaxterm-rebased/code/cache_acls.py
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/cache_acls.py (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/cache_acls.py (revision 2746)
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+from invirt.database import *
+from invirt.config import structs as config
+import sys
+import getafsgroups
+import subprocess
+
+def expandLocker(name):
+ try:
+ groups = getafsgroups.getLockerAcl(name)
+ except getafsgroups.AfsProcessError, e:
+ if e.message.startswith("fs: You don't have the required access rights on"):
+ return []
+ elif e.message.endswith("doesn't exist\n"):
+ # presumably deactivated
+ return []
+ else:
+ raise
+ cell = getafsgroups.getCell(name)
+ ans = set()
+ for group in groups:
+ if ':' in group:
+ ans.update(getafsgroups.getAfsGroupMembers(group, cell))
+ else:
+ ans.add(group)
+ return ans
+
+def isUser(name):
+ p = subprocess.Popen(['vos', 'examine', 'user.'+name],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if p.wait():
+ return False
+ return True
+
+
+def expandName(name):
+ if ':' not in name:
+ if isUser(name):
+ return [name]
+ return []
+ try:
+ return getafsgroups.getAfsGroupMembers(name, config.authz.afs.cells[0].cell)
+ except getafsgroups.AfsProcessError:
+ return []
+
+def accessList(m):
+ people = set()
+ people.update(expandLocker(m.owner))
+ if m.administrator is not None:
+ people.update(expandName(m.administrator))
+ return people
+
+def refreshMachine(m):
+ people = accessList(m)
+ old_people = set(a.user for a in m.acl)
+ for removed in old_people - people:
+ ma = [x for x in m.acl if x.user == removed][0]
+ session.delete(ma)
+ for p in people - old_people:
+ ma = MachineAccess(user=p)
+ m.acl.append(ma)
+ session.save_or_update(ma)
+
+def refreshCache():
+ session.begin()
+
+ try:
+ machines = Machine.query().all()
+ for m in machines:
+ refreshMachine(m)
+ session.flush()
+
+ # Update the admin ACL as well
+ admin_acl = set(expandName(config.adminacl))
+ old_admin_acl = set(a.user for a in Admin.query())
+ for removed in old_admin_acl - admin_acl:
+ old = Admin.query.filter_by(user=removed).first()
+ session.delete(old)
+ for added in admin_acl - old_admin_acl:
+ a = Admin(user=added)
+ session.save_or_update(a)
+ session.flush()
+
+ # Atomically execute our changes
+ session.commit()
+ except:
+ # Failed! Rollback all the changes.
+ session.rollback()
+ raise
+
+if __name__ == '__main__':
+ connect()
+ refreshCache()
Index: /package_branches/invirt-web/ajaxterm-rebased/code/controls.py
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/controls.py (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/controls.py (revision 2746)
@@ -0,0 +1,273 @@
+import validation
+from invirt.common import CodeError, InvalidInput
+import random
+import sys
+import time
+import re
+import cache_acls
+import yaml
+
+from invirt.config import structs as config
+from invirt.database import Machine, Disk, Type, NIC, CDROM, session, meta
+from invirt.remctl import remctl as gen_remctl
+
+# ... and stolen from xend/uuid.py
+def randomUUID():
+ """Generate a random UUID."""
+
+ return [ random.randint(0, 255) for _ in range(0, 16) ]
+
+def uuidToString(u):
+ """Turn a numeric UUID to a hyphen-seperated one."""
+ return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
+ "%02x" * 6]) % tuple(u)
+# end stolen code
+
+def remctl(*args, **kwargs):
+ return gen_remctl(config.remote.hostname,
+ principal='daemon/'+config.web.hostname,
+ *args, **kwargs)
+
+def lvcreate(machine, disk):
+ """Create a single disk for a machine"""
+ remctl('web', 'lvcreate', machine.name,
+ disk.guest_device_name, str(disk.size))
+
+def makeDisks(machine):
+ """Update the lvm partitions to add a disk."""
+ for disk in machine.disks:
+ lvcreate(machine, disk)
+
+def getswap(disksize, memsize):
+ """Returns the recommended swap partition size."""
+ return int(min(disksize / 4, memsize * 1.5))
+
+def lvinstall(machine, autoinstall):
+ disksize = machine.disks[0].size
+ memsize = machine.memory
+ swapsize = getswap(disksize, memsize)
+ imagesize = disksize - swapsize
+ ip = machine.nics[0].ip
+ remctl('control', machine.name, 'install',
+ 'dist=%s' % autoinstall.distribution,
+ 'mirror=%s' % autoinstall.mirror,
+ 'arch=%s' % autoinstall.arch,
+ 'imagesize=%s' % imagesize)
+
+def lvcopy(machine_orig_name, machine, rootpw):
+ """Copy a golden image onto a machine's disk"""
+ remctl('web', 'lvcopy', machine_orig_name, machine.name, rootpw)
+
+def bootMachine(machine, cdtype):
+ """Boot a machine with a given boot CD.
+
+ If cdtype is None, give no boot cd. Otherwise, it is the string
+ id of the CD (e.g. 'gutsy_i386')
+ """
+ if cdtype is not None:
+ out, err = remctl('control', machine.name, 'create',
+ cdtype, err=True)
+ else:
+ out, err = remctl('control', machine.name, 'create',
+ err=True)
+ if 'already running' in err:
+ raise InvalidInput('action', 'create',
+ 'VM %s is already on' % machine.name)
+ elif err:
+ raise CodeError('"%s" on "control %s create %s'
+ % (err, machine.name, cdtype))
+
+def createVm(username, state, owner, contact, name, description, memory, disksize, machine_type, cdrom, autoinstall):
+ """Create a VM and put it in the database"""
+ # put stuff in the table
+ session.begin()
+ try:
+ validation.Validate(username, state, name=name, description=description, owner=owner, memory=memory, disksize=disksize/1024.)
+ machine = Machine()
+ machine.name = name
+ machine.description = description
+ machine.memory = memory
+ machine.owner = owner
+ machine.administrator = None
+ machine.contact = contact
+ machine.uuid = uuidToString(randomUUID())
+ machine.boot_off_cd = True
+ machine.type = machine_type
+ session.save_or_update(machine)
+ disk = Disk(machine=machine,
+ guest_device_name='hda', size=disksize)
+ nic = NIC.query().filter_by(machine_id=None).filter_by(reusable=True).first()
+ if not nic: #No IPs left!
+ raise CodeError("No IP addresses left! "
+ "Contact %s." % config.web.errormail)
+ nic.machine = machine
+ nic.hostname = name
+ session.save_or_update(nic)
+ session.save_or_update(disk)
+ cache_acls.refreshMachine(machine)
+ makeDisks(machine)
+ session.commit()
+ except:
+ session.rollback()
+ raise
+ try:
+ if autoinstall:
+ lvinstall(machine, autoinstall)
+ else:
+ # tell it to boot with cdrom
+ bootMachine(machine, cdrom)
+ except CodeError, e:
+ deleteVM(machine)
+ raise
+ return machine
+
+def getList():
+ """Return a dictionary mapping machine names to dicts."""
+ value_string = remctl('web', 'listvms')
+ value_dict = yaml.load(value_string, yaml.CSafeLoader)
+ return value_dict
+
+def parseStatus(s):
+ """Parse a status string into nested tuples of strings.
+
+ s = output of xm list --long
Under the covers, the autoinstaller uses our own patched version of +xen-create-image, which is a tool based on debootstrap. If you log +into the serial console while the install is running, you can watch +it. +""", + 'ParaVM Console': """ +ParaVM machines do not support local console access over VNC. To +access the serial console of these machines, you can SSH with Kerberos +to %s, using the name of the machine as your +username.""" % config.console.hostname, + 'HVM/ParaVM': """ +HVM machines use the virtualization features of the processor, while +ParaVM machines rely on a modified kernel to communicate directly with +the hypervisor. HVMs support boot CDs of any operating system, and +the VNC console applet. The three-minute autoinstaller produces +ParaVMs. ParaVMs typically are more efficient, and always support the +console server.
+ +More details are on the +wiki, including steps to prepare an HVM guest to boot as a ParaVM +(which you can skip by using the autoinstaller to begin with.)
+ +We recommend using a ParaVM when possible and an HVM when necessary.
+""",
+ 'CPU Weight': """
+Don't ask us! We're as mystified as you are.""",
+ 'Owner': """
+The owner field is used to determine quotas. It must be the name of a
+locker that you are an AFS administrator of. In particular, you or an
+AFS group you are a member of must have AFS rlidwka bits on the
+locker. You can check who administers the LOCKER locker using the
+commands 'attach LOCKER; fs la /mit/LOCKER' on Athena.) See also administrator.""",
+ 'Administrator': """
+The administrator field determines who can access the console and
+power on and off the machine. This can be either a user or a moira
+group.""",
+ 'Quotas': """
+Quotas are determined on a per-locker basis. Each locker may have a
+maximum of 512 mebibytes of active ram, 50 gibibytes of disk, and 4
+active machines.""",
+ 'Console': """
+Framebuffer: At a Linux boot prompt in your VM, try
+setting fb=false to disable the framebuffer. If you don't,
+your machine will run just fine, but the applet's display of the
+console will suffer artifacts.
+""",
+ 'Windows': """
+Windows Vista: The Vista image is licensed for all MIT students and will automatically activate off the network; see the licensing confirmation e-mail for details. The installer requires 512 MiB RAM and at least 7.5 GiB disk space (15 GiB or more recommended).
+Windows XP: 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 http://msca.mit.edu/ if you are staff/faculty to request one.
+"""
+ }
+
+ if not subject:
+ subject = sorted(help_mapping.keys())
+ if not isinstance(subject, list):
+ subject = [subject]
+
+ return dict(simple=simple,
+ subjects=subject,
+ mapping=help_mapping)
+ help._cp_config['tools.require_login.on'] = False
+
+ def parseCreate(self, fields):
+ kws = dict([(kw, fields[kw]) for kw in
+ 'name description owner memory disksize vmtype cdrom autoinstall'.split()
+ if fields[kw]])
+ validate = validation.Validate(cherrypy.request.login,
+ cherrypy.request.state,
+ strict=True, **kws)
+ return dict(contact=cherrypy.request.login, name=validate.name,
+ description=validate.description, memory=validate.memory,
+ disksize=validate.disksize, owner=validate.owner,
+ machine_type=getattr(validate, 'vmtype', Defaults.type),
+ cdrom=getattr(validate, 'cdrom', None),
+ autoinstall=getattr(validate, 'autoinstall', None))
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/list.mako")
+ @cherrypy.tools.require_POST()
+ def create(self, **fields):
+ """Handler for create requests."""
+ try:
+ parsed_fields = self.parseCreate(fields)
+ machine = controls.createVm(cherrypy.request.login,
+ cherrypy.request.state, **parsed_fields)
+ except InvalidInput, err:
+ pass
+ else:
+ err = None
+ cherrypy.request.state.clear() #Changed global state
+ d = getListDict(cherrypy.request.login, cherrypy.request.state)
+ d['err'] = err
+ if err:
+ for field, value in fields.items():
+ setattr(d['defaults'], field, value)
+ else:
+ d['new_machine'] = parsed_fields['name']
+ return d
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/helloworld.mako")
+ def helloworld(self, **kwargs):
+ return {'request': cherrypy.request, 'kwargs': kwargs}
+ helloworld._cp_config['tools.require_login.on'] = False
+
+ @cherrypy.expose
+ def errortest(self):
+ """Throw an error, to test the error-tracing mechanisms."""
+ print >>sys.stderr, "look ma, it's a stderr"
+ raise RuntimeError("test of the emergency broadcast system")
+
+ class MachineView(View):
+ def __getattr__(self, name):
+ """Synthesize attributes to allow RESTful URLs like
+ /machine/13/info. This is hairy. CherryPy 3.2 adds a
+ method called _cp_dispatch that allows you to explicitly
+ handle URLs that can't be mapped, and it allows you to
+ rewrite the path components and continue processing.
+
+ This function gets the next path component being resolved
+ as a string. _cp_dispatch will get an array of strings
+ representing any subsequent path components as well."""
+
+ try:
+ cherrypy.request.params['machine_id'] = int(name)
+ return self
+ except ValueError:
+ return None
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/info.mako")
+ def info(self, machine_id):
+ """Handler for info on a single VM."""
+ machine = validation.Validate(cherrypy.request.login,
+ cherrypy.request.state,
+ machine_id=machine_id).machine
+ d = infoDict(cherrypy.request.login, cherrypy.request.state, machine)
+ return d
+ index = info
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/info.mako")
+ @cherrypy.tools.require_POST()
+ def modify(self, machine_id, **fields):
+ """Handler for modifying attributes of a machine."""
+ try:
+ modify_dict = modifyDict(cherrypy.request.login,
+ cherrypy.request.state,
+ machine_id, fields)
+ except InvalidInput, err:
+ result = None
+ machine = validation.Validate(cherrypy.request.login,
+ cherrypy.request.state,
+ machine_id=machine_id).machine
+ else:
+ machine = modify_dict['machine']
+ result = 'Success!'
+ err = None
+ info_dict = infoDict(cherrypy.request.login,
+ cherrypy.request.state, machine)
+ info_dict['err'] = err
+ if err:
+ for field, value in fields.items():
+ setattr(info_dict['defaults'], field, value)
+ info_dict['result'] = result
+ return info_dict
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/vnc.mako")
+ def vnc(self, machine_id):
+ """VNC applet page.
+
+ Note that due to same-domain restrictions, the applet connects to
+ the webserver, which needs to forward those requests to the xen
+ server. The Xen server runs another proxy that (1) authenticates
+ and (2) finds the correct port for the VM.
+
+ You might want iptables like:
+
+ -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
+ --dport 10003 -j DNAT --to-destination 18.181.0.60:10003
+ -t nat -A POSTROUTING -d 18.181.0.60 -o eth1 -p tcp -m tcp \
+ --dport 10003 -j SNAT --to-source 18.187.7.142
+ -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
+ --dport 10003 -j ACCEPT
+
+ Remember to enable iptables!
+ echo 1 > /proc/sys/net/ipv4/ip_forward
+ """
+ machine = validation.Validate(cherrypy.request.login,
+ cherrypy.request.state,
+ machine_id=machine_id).machine
+ token = controls.vnctoken(machine)
+ host = controls.listHost(machine)
+ if host:
+ port = 10003 + [h.hostname for h in config.hosts].index(host)
+ else:
+ port = 5900 # dummy
+
+ status = controls.statusInfo(machine)
+ has_vnc = hasVnc(status)
+
+ d = dict(on=status,
+ has_vnc=has_vnc,
+ machine=machine,
+ hostname=cherrypy.request.local.name,
+ port=port,
+ authtoken=token)
+ return d
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/command.mako")
+ @cherrypy.tools.require_POST()
+ def command(self, command_name, machine_id, **kwargs):
+ """Handler for running commands like boot and delete on a VM."""
+ back = kwargs.get('back')
+ if command_name == 'delete':
+ back = 'list'
+ try:
+ d = controls.commandResult(cherrypy.request.login,
+ cherrypy.request.state,
+ command_name, machine_id, kwargs)
+ except InvalidInput, err:
+ if not back:
+ raise
+ print >> sys.stderr, err
+ result = str(err)
+ else:
+ result = 'Success!'
+ if not back:
+ return d
+ if back == 'list':
+ cherrypy.request.state.clear() #Changed global state
+ raise cherrypy.InternalRedirect('/list?result=%s'
+ % urllib.quote(result))
+ elif back == 'info':
+ raise cherrypy.HTTPRedirect(cherrypy.request.base
+ + '/machine/%d/' % machine_id,
+ status=303)
+ else:
+ raise InvalidInput('back', back, 'Not a known back page.')
+
+ machine = MachineView()
+
+
+class Defaults:
+ """Class to store default values for fields."""
+ memory = 256
+ disk = 4.0
+ cdrom = ''
+ autoinstall = ''
+ name = ''
+ description = ''
+ administrator = ''
+ type = 'linux-hvm'
+
+ def __init__(self, max_memory=None, max_disk=None, **kws):
+ if max_memory is not None:
+ self.memory = min(self.memory, max_memory)
+ if max_disk is not None:
+ self.disk = min(self.disk, max_disk)
+ for key in kws:
+ setattr(self, key, kws[key])
+
+def hasVnc(status):
+ """Does the machine with a given status list support VNC?"""
+ if status is None:
+ return False
+ for l in status:
+ if l[0] == 'device' and l[1][0] == 'vfb':
+ d = dict(l[1][1:])
+ return 'location' in d
+ return False
+
+
+def getListDict(username, state):
+ """Gets the list of local variables used by list.tmpl."""
+ machines = state.machines
+ on = {}
+ has_vnc = {}
+ installing = {}
+ xmlist = state.xmlist
+ for m in machines:
+ if m not in xmlist:
+ has_vnc[m] = 'Off'
+ m.uptime = None
+ else:
+ m.uptime = xmlist[m]['uptime']
+ installing[m] = bool(xmlist[m].get('autoinstall'))
+ if xmlist[m]['console']:
+ has_vnc[m] = True
+ elif m.type.hvm:
+ has_vnc[m] = "WTF?"
+ else:
+ has_vnc[m] = "ParaVM"
+ max_memory = validation.maxMemory(username, state)
+ max_disk = validation.maxDisk(username)
+ defaults = Defaults(max_memory=max_memory,
+ max_disk=max_disk,
+ owner=username)
+ def sortkey(machine):
+ return (machine.owner != username, machine.owner, machine.name)
+ machines = sorted(machines, key=sortkey)
+ d = dict(user=username,
+ cant_add_vm=validation.cantAddVm(username, state),
+ max_memory=max_memory,
+ max_disk=max_disk,
+ defaults=defaults,
+ machines=machines,
+ has_vnc=has_vnc,
+ installing=installing)
+ return d
+
+def getHostname(nic):
+ """Find the hostname associated with a NIC.
+
+ XXX this should be merged with the similar logic in DNS and DHCP.
+ """
+ if nic.hostname:
+ hostname = nic.hostname
+ elif nic.machine:
+ hostname = nic.machine.name
+ else:
+ return None
+ if '.' in hostname:
+ return hostname
+ else:
+ return hostname + '.' + config.dns.domains[0]
+
+def getNicInfo(data_dict, machine):
+ """Helper function for info, get data on nics for a machine.
+
+ Modifies data_dict to include the relevant data, and returns a list
+ of (key, name) pairs to display "name: data_dict[key]" to the user.
+ """
+ data_dict['num_nics'] = len(machine.nics)
+ nic_fields_template = [('nic%s_hostname', 'NIC %s Hostname'),
+ ('nic%s_mac', 'NIC %s MAC Addr'),
+ ('nic%s_ip', 'NIC %s IP'),
+ ]
+ nic_fields = []
+ for i in range(len(machine.nics)):
+ nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
+ data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
+ data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
+ data_dict['nic%s_ip' % i] = machine.nics[i].ip
+ if len(machine.nics) == 1:
+ nic_fields = [(x, y.replace('NIC 0 ', '')) for x, y in nic_fields]
+ return nic_fields
+
+def getDiskInfo(data_dict, machine):
+ """Helper function for info, get data on disks for a machine.
+
+ Modifies data_dict to include the relevant data, and returns a list
+ of (key, name) pairs to display "name: data_dict[key]" to the user.
+ """
+ data_dict['num_disks'] = len(machine.disks)
+ disk_fields_template = [('%s_size', '%s size')]
+ disk_fields = []
+ for disk in machine.disks:
+ name = disk.guest_device_name
+ disk_fields.extend([(x % name, y % name) for x, y in
+ disk_fields_template])
+ data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
+ return disk_fields
+
+def modifyDict(username, state, machine_id, fields):
+ """Modify a machine as specified by CGI arguments.
+
+ Return a dict containing the machine that was modified.
+ """
+ olddisk = {}
+ session.begin()
+ try:
+ kws = dict([(kw, fields[kw]) for kw in
+ 'owner admin contact name description memory vmtype disksize'.split()
+ if fields[kw]])
+ kws['machine_id'] = machine_id
+ validate = validation.Validate(username, state, **kws)
+ machine = validate.machine
+ oldname = machine.name
+
+ if hasattr(validate, 'memory'):
+ machine.memory = validate.memory
+
+ if hasattr(validate, 'vmtype'):
+ machine.type = validate.vmtype
+
+ if hasattr(validate, 'disksize'):
+ disksize = validate.disksize
+ disk = machine.disks[0]
+ if disk.size != disksize:
+ olddisk[disk.guest_device_name] = disksize
+ disk.size = disksize
+ session.save_or_update(disk)
+
+ update_acl = False
+ if hasattr(validate, 'owner') and validate.owner != machine.owner:
+ machine.owner = validate.owner
+ update_acl = True
+ if hasattr(validate, 'name'):
+ machine.name = validate.name
+ for n in machine.nics:
+ if n.hostname == oldname:
+ n.hostname = validate.name
+ if hasattr(validate, 'description'):
+ machine.description = validate.description
+ if hasattr(validate, 'admin') and validate.admin != machine.administrator:
+ machine.administrator = validate.admin
+ update_acl = True
+ if hasattr(validate, 'contact'):
+ machine.contact = validate.contact
+
+ session.save_or_update(machine)
+ if update_acl:
+ cache_acls.refreshMachine(machine)
+ session.commit()
+ except:
+ session.rollback()
+ raise
+ for diskname in olddisk:
+ controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
+ if hasattr(validate, 'name'):
+ controls.renameMachine(machine, oldname, validate.name)
+ return dict(machine=machine)
+
+def infoDict(username, state, machine):
+ """Get the variables used by info.tmpl."""
+ status = controls.statusInfo(machine)
+ has_vnc = hasVnc(status)
+ if status is None:
+ main_status = dict(name=machine.name,
+ memory=str(machine.memory))
+ uptime = None
+ cputime = None
+ else:
+ main_status = dict(status[1:])
+ main_status['host'] = controls.listHost(machine)
+ start_time = float(main_status.get('start_time', 0))
+ uptime = datetime.timedelta(seconds=int(time.time()-start_time))
+ cpu_time_float = float(main_status.get('cpu_time', 0))
+ cputime = datetime.timedelta(seconds=int(cpu_time_float))
+ display_fields = [('name', 'Name'),
+ ('description', 'Description'),
+ ('owner', 'Owner'),
+ ('administrator', 'Administrator'),
+ ('contact', 'Contact'),
+ ('type', 'Type'),
+ 'NIC_INFO',
+ ('uptime', 'uptime'),
+ ('cputime', 'CPU usage'),
+ ('host', 'Hosted on'),
+ ('memory', 'RAM'),
+ 'DISK_INFO',
+ ('state', 'state (xen format)'),
+ ]
+ fields = []
+ machine_info = {}
+ machine_info['name'] = machine.name
+ machine_info['description'] = machine.description
+ machine_info['type'] = machine.type.hvm and 'HVM' or 'ParaVM'
+ machine_info['owner'] = machine.owner
+ machine_info['administrator'] = machine.administrator
+ machine_info['contact'] = machine.contact
+
+ nic_fields = getNicInfo(machine_info, machine)
+ nic_point = display_fields.index('NIC_INFO')
+ display_fields = (display_fields[:nic_point] + nic_fields +
+ display_fields[nic_point+1:])
+
+ disk_fields = getDiskInfo(machine_info, machine)
+ disk_point = display_fields.index('DISK_INFO')
+ display_fields = (display_fields[:disk_point] + disk_fields +
+ display_fields[disk_point+1:])
+
+ main_status['memory'] += ' MiB'
+ for field, disp in display_fields:
+ if field in ('uptime', 'cputime') and locals()[field] is not None:
+ fields.append((disp, locals()[field]))
+ elif field in machine_info:
+ fields.append((disp, machine_info[field]))
+ elif field in main_status:
+ fields.append((disp, main_status[field]))
+ else:
+ pass
+ #fields.append((disp, None))
+
+ max_mem = validation.maxMemory(machine.owner, state, machine, False)
+ max_disk = validation.maxDisk(machine.owner, machine)
+ defaults = Defaults()
+ for name in 'machine_id name description administrator owner memory contact'.split():
+ if getattr(machine, name):
+ setattr(defaults, name, getattr(machine, name))
+ defaults.type = machine.type.type_id
+ defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
+ d = dict(user=username,
+ on=status is not None,
+ machine=machine,
+ defaults=defaults,
+ has_vnc=has_vnc,
+ uptime=str(uptime),
+ ram=machine.memory,
+ max_mem=max_mem,
+ max_disk=max_disk,
+ fields = fields)
+ return d
+
+def send_error_mail(subject, body):
+ import subprocess
+
+ to = config.web.errormail
+ mail = """To: %s
+From: root@%s
+Subject: %s
+
+%s
+""" % (to, config.web.hostname, subject, body)
+ p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
+ stdin=subprocess.PIPE)
+ p.stdin.write(mail)
+ p.stdin.close()
+ p.wait()
+
+random.seed() #sigh
Index: /package_branches/invirt-web/ajaxterm-rebased/code/static/VncViewer.jar
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/static/VncViewer.jar (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/static/VncViewer.jar (revision 2746)
@@ -0,0 +1,1 @@
+link /usr/share/invirt-vnc-client/VncViewer.jar
Index: /package_branches/invirt-web/ajaxterm-rebased/code/static/layout.css
===================================================================
--- /package_branches/invirt-web/ajaxterm-rebased/code/static/layout.css (revision 2746)
+++ /package_branches/invirt-web/ajaxterm-rebased/code/static/layout.css (revision 2746)
@@ -0,0 +1,30 @@
+/*
+ Good layout ideas stolen from Debathena.
+ Hey, we use some different fonts (in style.css.)
+ And the background color is unmistakably different.
+ Some other things are tweaked too.
+*/
+
+/* This file contains screen-only layout declarations that won't be
+ used for printing. */
+
+/* Make