-DIRS = templates
-
-all: kill chmod compile
+all: kill chmod
chmod:
chgrp -R invirt . 2>/dev/null || true
kill:
-pkill main.fcgi
-
-compile:
- for dir in $(DIRS); do \
- (cd $$dir; $(MAKE) all); \
- done
-
-clean:
- for dir in $(DIRS); do \
- (cd $$dir; $(MAKE) clean); \
- done
--- /dev/null
+invirt.fcgi
\ No newline at end of file
session.rollback()
raise
-def commandResult(username, state, fields):
+def commandResult(username, state, command_name, machine_id, fields):
start_time = 0
- machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
- action = fields.getfirst('action')
- cdrom = fields.getfirst('cdrom')
+ machine = validation.Validate(username, state, machine_id=machine_id).machine
+ action = command_name
+ cdrom = fields.get('cdrom') or None
if cdrom is not None and not CDROM.query().filter_by(cdrom_id=cdrom).one():
raise CodeError("Invalid cdrom type '%s'" % cdrom)
- if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown',
- 'Delete VM'):
+ if action not in "reboot create destroy shutdown delete".split(" "):
raise CodeError("Invalid action '%s'" % action)
- if action == 'Reboot':
+ if action == 'reboot':
if cdrom is not None:
out, err = remctl('control', machine.name, 'reboot', cdrom,
err=True)
print >> sys.stderr, err
raise CodeError('ERROR on remctl')
- elif action == 'Power on':
+ elif action == 'create':
if validation.maxMemory(username, state, machine) < machine.memory:
raise InvalidInput('action', 'Power on',
"You don't have enough free RAM quota "
"to turn on this machine.")
bootMachine(machine, cdrom)
- elif action == 'Power off':
+ elif action == 'destroy':
out, err = remctl('control', machine.name, 'destroy', err=True)
if err:
if re.match("machine '.*' is not on", err):
print >> sys.stderr, 'Error on power off:'
print >> sys.stderr, err
raise CodeError('ERROR on remctl')
- elif action == 'Shutdown':
+ elif action == 'shutdown':
out, err = remctl('control', machine.name, 'shutdown', err=True)
if err:
if re.match("machine '.*' is not on", err):
print >> sys.stderr, 'Error on Shutdown:'
print >> sys.stderr, err
raise CodeError('ERROR on remctl')
- elif action == 'Delete VM':
+ elif action == 'delete':
deleteVM(machine)
d = dict(user=username,
--- /dev/null
+# For the testing site.
+
+[global]
+server.socket_port = 8080
+log.access_file = "/tmp/invirt-web-access_log.dev"
+log.error_file = "/tmp/invirt-web-error_log.dev"
+tools.mako.module_directory = "/tmp/invirt-web-templatecache.dev"
+tools.basic_auth.on = True
+tools.basic_auth.realm = 'Invirt Web DEVELOPMENT'
+tools.basic_auth.users = {'quentin': 'quentin','broder': 'broder'}
+tools.basic_auth.encrypt = __builtin__.str
for c in config.authz.afs.cells:
if c.cell == cell and hasattr(c, 'auth'):
encrypt = c.auth
- subprocess.check_call(['aklog', cell], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if encrypt:
+ subprocess.check_call(['aklog', cell], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(["pts", "membership", "-encrypt" if encrypt else '-noauth', group, '-c', cell],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
err = p.stderr.read()
--- /dev/null
+#!/usr/bin/python
+"""Main FastCGI entry point for web interface"""
+
+import cherrypy
+import os
+import sys
+
+import main
+
+dev = False
+base_dir = os.path.dirname(__file__)
+
+def usage():
+ print >>sys.stderr, """%s [config]
+
+Run server as FastCGI, with CherryPy config from "main.conf".
+
+With `config`, run standalone with CherryPy config from `config`.
+
+Run this script as either 'auth.fcgi' or 'unauth.fcgi', to get
+the authenticated or unauthenticated site respectively.
+""" % sys.argv[0]
+ sys.exit(2)
+
+if __name__ == "__main__":
+ if len(sys.argv) > 2:
+ usage()
+ if len(sys.argv) > 1:
+ if sys.argv[1] in ('-h', '--help'):
+ usage()
+ conf_file = sys.argv[1]
+ dev = True
+ else:
+ conf_file = os.path.join(base_dir, 'main.conf')
+
+ app_config = {
+ '/': {
+ 'tools.invirtwebstate.on': True,
+ },
+ }
+
+ if os.path.basename(sys.argv[0]).startswith('auth'):
+ root = main.InvirtWeb()
+ elif os.path.basename(sys.argv[0]).startswith('unauth'):
+ root = main.InvirtUnauthWeb()
+ else:
+ usage()
+
+ app = cherrypy.tree.mount(root, '/', app_config)
+ app.merge(conf_file)
+ cherrypy.config.update(conf_file)
+
+ if dev:
+ cherrypy.server.quickstart()
+ cherrypy.engine.start()
+ cherrypy.engine.block()
+ else:
+ cherrypy.engine.start(blocking=False)
+ from flup.server.fcgi import WSGIServer
+ server = WSGIServer(cherrypy.tree)
+ server.run()
--- /dev/null
+# This file is used when the web interface is loaded as a FastCGI
+[global]
+#auto_reload doesn't work with FastCGI
+engine.auto_reload = False
+tools.mako.module_directory = "/tmp/invirt-web-templatecache"
+tools.remote_user_login.on = True
+
+engine.SIGHUP = None
+engine.SIGTERM = None
+
+log.screen = False
+++ /dev/null
-#!/usr/bin/python
-import main
-main.main()
import cgi
import datetime
import hmac
+import os
import random
import sha
-import simplejson
import sys
import time
import urllib
import socket
+import cherrypy
+from cherrypy import _cperror
from StringIO import StringIO
-def revertStandardError():
- """Move stderr to stdout, and return the contents of the old stderr."""
- errio = sys.stderr
- if not isinstance(errio, StringIO):
- return ''
- sys.stderr = sys.stdout
- errio.seek(0)
- return errio.read()
-
def printError():
"""Revert stderr to stdout, and print the contents of stderr"""
if isinstance(sys.stderr, StringIO):
import atexit
atexit.register(printError)
-import templates
-from Cheetah.Template import Template
import validation
import cache_acls
from webcommon import State
from invirt.config import structs as config
from invirt.common import InvalidInput, CodeError
-def pathSplit(path):
- if path.startswith('/'):
- path = path[1:]
- i = path.find('/')
- if i == -1:
- i = len(path)
- return path[:i], path[i:]
+from view import View, revertStandardError
+
+
+static_dir = os.path.join(os.path.dirname(__file__), 'static')
+InvirtStatic = cherrypy.tools.staticdir.handler(
+ root=static_dir,
+ dir=static_dir,
+ section='/static')
-class Checkpoint:
+class InvirtUnauthWeb(View):
+ static = InvirtStatic
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/unauth.mako")
+ def index(self):
+ return {'simple': True}
+
+class InvirtWeb(View):
def __init__(self):
- self.start_time = time.time()
- self.checkpoints = []
-
- def checkpoint(self, s):
- self.checkpoints.append((s, time.time()))
-
- def __str__(self):
- return ('Timing info:\n%s\n' %
- '\n'.join(['%s: %s' % (d, t - self.start_time) for
- (d, t) in self.checkpoints]))
-
-checkpoint = Checkpoint()
-
-def jquote(string):
- return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
-
-def helppopup(subj):
- """Return HTML code for a (?) link to a specified help topic"""
- return ('<span class="helplink"><a href="help?' +
- cgi.escape(urllib.urlencode(dict(subject=subj, simple='true')))
- +'" target="_blank" ' +
- 'onclick="return helppopup(' + cgi.escape(jquote(subj)) + ')">(?)</a></span>')
-
-def makeErrorPre(old, addition):
- if addition is None:
- return
- if old:
- return old[:-6] + '\n----\n' + str(addition) + '</pre>'
- else:
- return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
-
-Template.database = database
-Template.config = config
-Template.helppopup = staticmethod(helppopup)
-Template.err = None
-
-class JsonDict:
- """Class to store a dictionary that will be converted to JSON"""
- def __init__(self, **kws):
- self.data = kws
- if 'err' in kws:
- err = kws['err']
- del kws['err']
- self.addError(err)
-
- def __str__(self):
- return simplejson.dumps(self.data)
-
- def addError(self, text):
- """Add stderr text to be displayed on the website."""
- self.data['err'] = \
- makeErrorPre(self.data.get('err'), text)
+ super(self.__class__,self).__init__()
+ connect()
+ self._cp_config['tools.require_login.on'] = True
+ self._cp_config['tools.catch_stderr.on'] = True
+ self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config',
+ 'from invirt import database']
+ self._cp_config['request.error_response'] = self.handle_error
+
+ static = InvirtStatic
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/invalid.mako")
+ def invalidInput(self):
+ """Print an error page when an InvalidInput exception occurs"""
+ err = cherrypy.request.prev.params["err"]
+ emsg = cherrypy.request.prev.params["emsg"]
+ d = dict(err_field=err.err_field,
+ err_value=str(err.err_value), stderr=emsg,
+ errorMessage=str(err))
+ return d
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/error.mako")
+ def error(self):
+ """Print an error page when an exception occurs"""
+ op = cherrypy.request.prev.path_info
+ username = cherrypy.request.login
+ err = cherrypy.request.prev.params["err"]
+ emsg = cherrypy.request.prev.params["emsg"]
+ traceback = cherrypy.request.prev.params["traceback"]
+ d = dict(op=op, user=username, fields=cherrypy.request.prev.params,
+ errorMessage=str(err), stderr=emsg, traceback=traceback)
+ error_raw = cherrypy.request.lookup.get_template("/error_raw.mako")
+ details = error_raw.render(**d)
+ exclude = config.web.errormail_exclude
+ if username not in exclude and '*' not in exclude:
+ send_error_mail('xvm error on %s for %s: %s' % (op, cherrypy.request.login, err),
+ details)
+ d['details'] = details
+ return d
+
+ def __getattr__(self, name):
+ if name in ("admin", "overlord"):
+ if not cherrypy.request.login in getAfsGroupMembers(config.adminacl, config.authz.afs.cells[0].cell):
+ raise InvalidInput('username', cherrypy.request.login,
+ 'Not in admin group %s.' % config.adminacl)
+ cherrypy.request.state = State(cherrypy.request.login, isadmin=True)
+ return self
+ else:
+ return super(InvirtWeb, self).__getattr__(name)
+
+ def handle_error(self):
+ err = sys.exc_info()[1]
+ if isinstance(err, InvalidInput):
+ cherrypy.request.params['err'] = err
+ cherrypy.request.params['emsg'] = revertStandardError()
+ raise cherrypy.InternalRedirect('/invalidInput')
+ if not cherrypy.request.prev or 'err' not in cherrypy.request.prev.params:
+ cherrypy.request.params['err'] = err
+ cherrypy.request.params['emsg'] = revertStandardError()
+ cherrypy.request.params['traceback'] = _cperror.format_exc()
+ raise cherrypy.InternalRedirect('/error')
+ # fall back to cherrypy default error page
+ cherrypy.HTTPError(500).set_response()
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/list.mako")
+ def list(self, result=None):
+ """Handler for list requests."""
+ d = getListDict(cherrypy.request.login, cherrypy.request.state)
+ if result is not None:
+ d['result'] = result
+ return d
+ index=list
+
+ @cherrypy.expose
+ @cherrypy.tools.mako(filename="/help.mako")
+ def help(self, subject=None, simple=False):
+ """Handler for help messages."""
+
+ help_mapping = {
+ 'Autoinstalls': """
+The autoinstaller builds a minimal Debian or Ubuntu system to run as a
+ParaVM. You can access the resulting system by logging into the <a
+href="help?simple=true&subject=ParaVM+Console">serial console server</a>
+with your Kerberos tickets; there is no root password so sshd will
+refuse login.</p>
+
+<p>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
+<a href="help?subject=ParaVM+Console">console server</a>.</p>
+
+<p>More details are <a
+href="https://xvm.scripts.mit.edu/wiki/Paravirtualization">on the
+wiki</a>, including steps to prepare an HVM guest to boot as a ParaVM
+(which you can skip by using the autoinstaller to begin with.)</p>
+
+<p>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 <a
+href="help?subject=Quotas">quotas</a>. 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 <a
+href="help?subject=Administrator">administrator</a>.""",
+ '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': """
+<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
+setting <tt>fb=false</tt> 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': """
+<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>
+<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.
+"""
+ }
+
+ 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."""
autoinstall = ''
name = ''
description = ''
+ administrator = ''
type = 'linux-hvm'
def __init__(self, max_memory=None, max_disk=None, **kws):
for key in kws:
setattr(self, key, kws[key])
-
-
-DEFAULT_HEADERS = {'Content-Type': 'text/html'}
-
-def invalidInput(op, username, fields, err, emsg):
- """Print an error page when an InvalidInput exception occurs"""
- d = dict(op=op, user=username, err_field=err.err_field,
- err_value=str(err.err_value), stderr=emsg,
- errorMessage=str(err))
- return templates.invalid(searchList=[d])
-
def hasVnc(status):
"""Does the machine with a given status list support VNC?"""
if status is None:
return 'location' in d
return False
-def parseCreate(username, state, fields):
- kws = dict([(kw, fields.getfirst(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
- validate = validation.Validate(username, state, strict=True, **kws)
- return dict(contact=username, 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))
-
-def create(username, state, path, fields):
- """Handler for create requests."""
- try:
- parsed_fields = parseCreate(username, state, fields)
- machine = controls.createVm(username, state, **parsed_fields)
- except InvalidInput, err:
- pass
- else:
- err = None
- state.clear() #Changed global state
- d = getListDict(username, state)
- d['err'] = err
- if err:
- for field in fields.keys():
- setattr(d['defaults'], field, fields.getfirst(field))
- else:
- d['new_machine'] = parsed_fields['name']
- return templates.list(searchList=[d])
-
def getListDict(username, state):
"""Gets the list of local variables used by list.tmpl."""
- checkpoint.checkpoint('Starting')
machines = state.machines
- checkpoint.checkpoint('Got my machines')
on = {}
has_vnc = {}
+ installing = {}
xmlist = state.xmlist
- checkpoint.checkpoint('Got uptimes')
- can_clone = 'ice3' not in state.xmlist_raw
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"+helppopup("ParaVM Console")
+ has_vnc[m] = "ParaVM"
max_memory = validation.maxMemory(username, state)
max_disk = validation.maxDisk(username)
- checkpoint.checkpoint('Got max mem/disk')
defaults = Defaults(max_memory=max_memory,
max_disk=max_disk,
owner=username)
- checkpoint.checkpoint('Got defaults')
def sortkey(machine):
return (machine.owner != username, machine.owner, machine.name)
machines = sorted(machines, key=sortkey)
defaults=defaults,
machines=machines,
has_vnc=has_vnc,
- can_clone=can_clone)
+ installing=installing)
return d
-def listVms(username, state, path, fields):
- """Handler for list requests."""
- checkpoint.checkpoint('Getting list dict')
- d = getListDict(username, state)
- checkpoint.checkpoint('Got list dict')
- return templates.list(searchList=[d])
-
-def vnc(username, state, path, fields):
- """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(username, state, machine_id=fields.getfirst('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(user=username,
- on=status,
- has_vnc=has_vnc,
- machine=machine,
- hostname=state.environ.get('SERVER_NAME', 'localhost'),
- port=port,
- authtoken=token)
- return templates.vnc(searchList=[d])
-
def getHostname(nic):
"""Find the hostname associated with a NIC.
data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
return disk_fields
-def command(username, state, path, fields):
- """Handler for running commands like boot and delete on a VM."""
- back = fields.getfirst('back')
- try:
- d = controls.commandResult(username, state, fields)
- if d['command'] == 'Delete VM':
- back = 'list'
- except InvalidInput, err:
- if not back:
- raise
- print >> sys.stderr, err
- result = err
- else:
- result = 'Success!'
- if not back:
- return templates.command(searchList=[d])
- if back == 'list':
- state.clear() #Changed global state
- d = getListDict(username, state)
- d['result'] = result
- return templates.list(searchList=[d])
- elif back == 'info':
- machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
- return ({'Status': '303 See Other',
- 'Location': 'info?machine_id=%d' % machine.machine_id},
- "You shouldn't see this message.")
- else:
- raise InvalidInput('back', back, 'Not a known back page.')
-
-def modifyDict(username, state, fields):
+def modifyDict(username, state, machine_id, fields):
"""Modify a machine as specified by CGI arguments.
- Return a list of local variables for modify.tmpl.
+ Return a dict containing the machine that was modified.
"""
olddisk = {}
session.begin()
try:
- kws = dict([(kw, fields.getfirst(kw)) for kw in 'machine_id owner admin contact name description memory vmtype disksize'.split()])
+ 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
controls.resizeDisk(oldname, diskname, str(olddisk[diskname]))
if hasattr(validate, 'name'):
controls.renameMachine(machine, oldname, validate.name)
- return dict(user=username,
- command="modify",
- machine=machine)
-
-def modify(username, state, path, fields):
- """Handler for modifying attributes of a machine."""
- try:
- modify_dict = modifyDict(username, state, fields)
- except InvalidInput, err:
- result = None
- machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
- else:
- machine = modify_dict['machine']
- result = 'Success!'
- err = None
- info_dict = infoDict(username, state, machine)
- info_dict['err'] = err
- if err:
- for field in fields.keys():
- setattr(info_dict['defaults'], field, fields.getfirst(field))
- info_dict['result'] = result
- return templates.info(searchList=[info_dict])
-
-
-def helpHandler(username, state, path, fields):
- """Handler for help messages."""
- simple = fields.getfirst('simple')
- subjects = fields.getlist('subject')
-
- help_mapping = {
- 'Autoinstalls': """
-The autoinstaller builds a minimal Debian or Ubuntu system to run as a
-ParaVM. You can access the resulting system by logging into the <a
-href="help?simple=true&subject=ParaVM+Console">serial console server</a>
-with your Kerberos tickets; there is no root password so sshd will
-refuse login.</p>
-
-<p>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
-<a href="help?subject=ParaVM+Console">console server</a>.</p>
-
-<p>More details are <a
-href="https://xvm.scripts.mit.edu/wiki/Paravirtualization">on the
-wiki</a>, including steps to prepare an HVM guest to boot as a ParaVM
-(which you can skip by using the autoinstaller to begin with.)</p>
-
-<p>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 <a
-href="help?subject=Quotas">quotas</a>. 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 <a
-href="help?subject=Administrator">administrator</a>.""",
- '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': """
-<strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
-setting <tt>fb=false</tt> 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': """
-<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>
-<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.
-"""
- }
-
- if not subjects:
- subjects = sorted(help_mapping.keys())
-
- d = dict(user=username,
- simple=simple,
- subjects=subjects,
- mapping=help_mapping)
-
- return templates.help(searchList=[d])
-
-
-def badOperation(u, s, p, e):
- """Function called when accessing an unknown URI."""
- return ({'Status': '404 Not Found'}, 'Invalid operation.')
+ return dict(machine=machine)
def infoDict(username, state, machine):
"""Get the variables used by info.tmpl."""
status = controls.statusInfo(machine)
- checkpoint.checkpoint('Getting status info')
has_vnc = hasVnc(status)
if status is None:
main_status = dict(name=machine.name,
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))
- checkpoint.checkpoint('Status')
display_fields = [('name', 'Name'),
('description', 'Description'),
('owner', 'Owner'),
('memory', 'RAM'),
'DISK_INFO',
('state', 'state (xen format)'),
- ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
]
fields = []
machine_info = {}
pass
#fields.append((disp, None))
- checkpoint.checkpoint('Got fields')
-
-
max_mem = validation.maxMemory(machine.owner, state, machine, False)
- checkpoint.checkpoint('Got mem')
max_disk = validation.maxDisk(machine.owner, machine)
defaults = Defaults()
for name in 'machine_id name description administrator owner memory contact'.split():
- setattr(defaults, name, getattr(machine, name))
+ 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.)
- checkpoint.checkpoint('Got defaults')
d = dict(user=username,
on=status is not None,
machine=machine,
ram=machine.memory,
max_mem=max_mem,
max_disk=max_disk,
- owner_help=helppopup("Owner"),
fields = fields)
return d
-def info(username, state, path, fields):
- """Handler for info on a single VM."""
- machine = validation.Validate(username, state, machine_id=fields.getfirst('machine_id')).machine
- d = infoDict(username, state, machine)
- checkpoint.checkpoint('Got infodict')
- return templates.info(searchList=[d])
-
-def unauthFront(_, _2, _3, fields):
- """Information for unauth'd users."""
- return templates.unauth(searchList=[{'simple' : True,
- 'hostname' : socket.getfqdn()}])
-
-def admin(username, state, path, fields):
- if path == '':
- return ({'Status': '303 See Other',
- 'Location': 'admin/'},
- "You shouldn't see this message.")
- if not username in getAfsGroupMembers(config.adminacl, 'athena.mit.edu'):
- raise InvalidInput('username', username,
- 'Not in admin group %s.' % config.adminacl)
- newstate = State(username, isadmin=True)
- newstate.environ = state.environ
- return handler(username, newstate, path, fields)
-
-def throwError(_, __, ___, ____):
- """Throw an error, to test the error-tracing mechanisms."""
- raise RuntimeError("test of the emergency broadcast system")
-
-mapping = dict(list=listVms,
- vnc=vnc,
- command=command,
- modify=modify,
- info=info,
- create=create,
- help=helpHandler,
- unauth=unauthFront,
- admin=admin,
- overlord=admin,
- errortest=throwError)
-
-def printHeaders(headers):
- """Print a dictionary as HTTP headers."""
- for key, value in headers.iteritems():
- print '%s: %s' % (key, value)
- print
-
def send_error_mail(subject, body):
import subprocess
p.stdin.close()
p.wait()
-def show_error(op, username, fields, err, emsg, traceback):
- """Print an error page when an exception occurs"""
- d = dict(op=op, user=username, fields=fields,
- errorMessage=str(err), stderr=emsg, traceback=traceback)
- details = templates.error_raw(searchList=[d])
- exclude = config.web.errormail_exclude
- if username not in exclude and '*' not in exclude:
- send_error_mail('xvm error on %s for %s: %s' % (op, username, err),
- details)
- d['details'] = details
- return templates.error(searchList=[d])
-
-def getUser(environ):
- """Return the current user based on the SSL environment variables"""
- user = environ.get('REMOTE_USER')
- if user is None:
- return
-
- if environ.get('AUTH_TYPE') == 'Negotiate':
- # Convert the krb5 principal into a krb4 username
- if not user.endswith('@%s' % config.kerberos.realm):
- return
- else:
- return user.split('@')[0].replace('/', '.')
- else:
- return user
-
-def handler(username, state, path, fields):
- operation, path = pathSplit(path)
- if not operation:
- operation = 'list'
- print 'Starting', operation
- fun = mapping.get(operation, badOperation)
- return fun(username, state, path, fields)
-
-class App:
- def __init__(self, environ, start_response):
- self.environ = environ
- self.start = start_response
-
- self.username = getUser(environ)
- self.state = State(self.username)
- self.state.environ = environ
-
- random.seed() #sigh
-
- def __iter__(self):
- start_time = time.time()
- database.clear_cache()
- sys.stderr = StringIO()
- fields = cgi.FieldStorage(fp=self.environ['wsgi.input'], environ=self.environ)
- operation = self.environ.get('PATH_INFO', '')
- if not operation:
- self.start("301 Moved Permanently", [('Location', './')])
- return
- if self.username is None:
- operation = 'unauth'
-
- try:
- checkpoint.checkpoint('Before')
- output = handler(self.username, self.state, operation, fields)
- checkpoint.checkpoint('After')
-
- headers = dict(DEFAULT_HEADERS)
- if isinstance(output, tuple):
- new_headers, output = output
- headers.update(new_headers)
- e = revertStandardError()
- if e:
- if hasattr(output, 'addError'):
- output.addError(e)
- else:
- # This only happens on redirects, so it'd be a pain to get
- # the message to the user. Maybe in the response is useful.
- output = output + '\n\nstderr:\n' + e
- output_string = str(output)
- checkpoint.checkpoint('output as a string')
- except Exception, err:
- if not fields.has_key('js'):
- if isinstance(err, InvalidInput):
- self.start('200 OK', [('Content-Type', 'text/html')])
- e = revertStandardError()
- yield str(invalidInput(operation, self.username, fields,
- err, e))
- return
- import traceback
- self.start('500 Internal Server Error',
- [('Content-Type', 'text/html')])
- e = revertStandardError()
- s = show_error(operation, self.username, fields,
- err, e, traceback.format_exc())
- yield str(s)
- return
- status = headers.setdefault('Status', '200 OK')
- del headers['Status']
- self.start(status, headers.items())
- yield output_string
- if fields.has_key('timedebug'):
- yield '<pre>%s</pre>' % cgi.escape(str(checkpoint))
-
-def constructor():
- connect()
- return App
-
-def main():
- from flup.server.fcgi_fork import WSGIServer
- WSGIServer(constructor()).run()
-
-if __name__ == '__main__':
- main()
+random.seed() #sigh
#machinelist td, #machinelist th {
padding: 0.1em 0.5em;
}
+
+form {
+ display: inline;
+}
+++ /dev/null
-TEMPLATES=$(wildcard *.tmpl)
-OUTPUTS=$(TEMPLATES:.tmpl=.py)
-
-all: ${OUTPUTS}
-
-%.py: %.tmpl
- cheetah compile $<
-
-#${OUTPUTS}:${TEMPLATES}
-# cheetah compile $^
-
-clean:
- @rm -f ${OUTPUTS} *.pyo *.pyc *.py.bak
+++ /dev/null
-__all__ = 'info command error error_raw help invalid list unauth vnc'.split()
-for _name in __all__:
- try:
- _module = __import__(_name, globals(), {}, [_name])
- globals()[_name] = getattr(_module, _name)
- except ImportError, e:
- import sys
- print >> sys.stderr, 'Importing template "%s" raised error: %s' % (_name, e)
-
--- /dev/null
+<%page expression_filter="h" />
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+$command ${machine.name}
+</%def>
+
+<p>${command} ${machine.name} was successful.</p>
+<p><a href="list">Return</a></p>
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-$command $machine.name
-#end def
-
-
-#def body
-<p>$command ${machine.name} was successful.</p>
-#if $command == "Delete VM" or True
-<p><a href="list">Return</a></p>
-#else
-<p><a href="info?machine_id=${machine.machine_id}">Return</a></p>
-#end if
-#end def
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Created!
-#end def
-
-#def body
-#if $machine
-<p>Congratulations! You have a new machine named ${machine.name}.</p>
-#else
-<p>Odd... no error, but no machine.</p>
-#end if
-<p><a href="list">Return</a></p>
-#end def
-#from skeleton import skeleton
-#extends skeleton
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
-#def title
+<%def name="title()">
ERROR!
-#end def
+</%def>
-#def body
<p>Uh-oh! We experienced an error. Sorry about that. We've gotten
mail about it.</p>
-<p>Feel free to poke us at <tt>xvm@mit.edu</tt> if this bug is
+<p>Feel free to poke us at <tt>${config.contact}</tt> if this bug is
consistently biting you and we don't seem to be fixing it.</p>
<p>In case you're curious, the gory details are below.</p>
<pre>
-$details
+${details}
</pre>
-#end def
--- /dev/null
+Error on operation ${op} for user ${user}: ${errorMessage}
+
+Fields:
+%for f in fields:
+${f}=${fields[f]}
+%endfor
+
+Error output:
+${stderr}\
+---- end error output
+
+${traceback}
+++ /dev/null
-Error on operation $op for user $user: $errorMessage
-
-Fields:
-#for $f in $fields:
-$f=$fields[$f].value
-#end for
-
-Error output:
-$stderr#slurp
----- end error output
-
-$traceback
--- /dev/null
+<%page expression_filter="h"/>
+<%def name="databaseList(lst, default, onchange, name, id, valueattr, descattr)">
+<select name="${name}" id="${id}" \
+% if onchange:
+onchange="${onchange}"\
+% endif
+>
+ <option ${'' if default else 'selected'} value="">None</option>
+ %for item in lst:
+ <option ${'selected' if default == getattr(item, valueattr) else ''} value="${getattr(item, valueattr)}">
+ ${getattr(item, descattr)}
+ </option>
+ % endfor
+</select>
+</%def>
+
+<%def name="cdromList(default='', onchange=None)">
+${databaseList(sorted(database.CDROM.query(), key=lambda x: x.description),
+ default, onchange, 'cdrom', 'cdromlist', 'cdrom_id', 'description')|n}
+</%def>
+
+<%def name="autoList(default='', onchange=None)">
+${databaseList(sorted(database.Autoinstall.query(), key=lambda x: x.description),
+ default, onchange, 'autoinstall', 'autoinstalllist', 'autoinstall_id', 'description')|n}
+</%def>
+
+<%def name="vmTypeList(default=None)">
+% for vmtype in (('linux-hvm', 'HVM'), ('linux', 'ParaVM'), ):
+<label>
+ <input ${'checked="checked"' if default == vmtype[0] else ''} type="radio" name="vmtype" id="vmtype-${vmtype[0]}" value="${vmtype[0]}">${vmtype[1]}</input>
+</label>
+% endfor
+</%def>
+
+<%def name="errorRow(value, err)">
+% if err and err.err_field == value:
+<tr>
+<td class="error" colspan="2">${str(err)}</td>
+</tr>
+% endif
+</%def>
+
+<%!
+def jquote(string):
+ return "'" + string.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n') + "'"
+
+def nl2br(string):
+ return string.replace('\n', '<br/>')
+%>
+
+<%def name="helppopup(subj)">
+## Return HTML code for a (?) link to a specified help topic
+<span class="helplink"><a href="help?simple=true;subject=${subj | u}" target="_blank" onclick="return helppopup(${subj | u,jquote})">(?)</a></span>
+</%def>
+++ /dev/null
-#filter WebSafe
-#def databaseList($lst, $default, $onchange, $name, $id, $valueattr, $descattr)
-<select name="$name" id="$id"#slurp
-#if $onchange is not None
-onchange="$onchange"#slurp
-#end if
->
- <option #slurp
-#if $default then '' else 'selected'
- value="">None</option>
- #for $item in $lst
- <option #slurp
-#if $default == getattr(item, valueattr) then 'selected' else ''
- value="${getattr(item, valueattr)}">
- ${getattr(item, descattr)}
- </option>
- #end for
-</select>
-#end def
-
-#def cdromList($default="", $onchange=None)
-#filter None
-$databaseList(sorted($database.CDROM.query(), key=lambda x: x.description),
- default, onchange, 'cdrom', 'cdromlist', 'cdrom_id', 'description')
-#end filter
-#end def
-
-#def autoList($default="", $onchange=None)
-#filter None
-$databaseList(sorted($database.Autoinstall.query(), key=lambda x: x.description),
- default, onchange, 'autoinstall', 'autoinstalllist', 'autoinstall_id', 'description')
-#end filter
-#end def
-
-#def vmTypeList($default=None)
-#for $vmtype in (('linux-hvm', 'HVM'), ('linux', 'ParaVM'), )
-<label>
- <input #slurp
-#if $default == $vmtype[0] then 'checked="checked"' else ''
- type="radio" name="vmtype" id="vmtype-${vmtype[0]}" value="${vmtype[0]}">${vmtype[1]}</input>
-</label>
-#end for
-#end def
-
-#def addError(txt)
-#if $varExists('txt')
-#set global $error_text = $error_text + '----\n' + $txt
-#else
-#set global $error_text = $txt
-#end if
-#end def
-
-#def errorRow($value, $err)
-#if $err and $err.err_field == $value
-<tr>
-<td class="error" colspan="2">${str($err)}</td>
-</tr>
-#end if
-#end def
-#filter None
-$full_body
-#end filter
-#end filter
--- /dev/null
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<p>Hello world!</p>
+
+<p>kwargs:</p>
+<pre style="white-space: pre-wrap">
+${repr(kwargs)}
+</pre>
+
+<p>Your request:</p>
+
+<pre style="white-space: pre-wrap">
+${repr(dir(request))}
+</pre>
+
+<%def name="title()">
+helloworld
+</%def>
--- /dev/null
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%!
+ pageclass = 'help'
+%>
+
+<%def name="title()">
+% if len(subjects) == 1:
+Help on ${subjects[0]}
+% else:
+Help
+% endif
+</%def>
+
+% if not simple:
+<p>Topics:
+% for key in sorted(mapping):
+<a href="help?subject=${key}">${key}</a>
+% endfor
+</p>
+<p>
+See also <a href="trac/wiki/tips">tips and HOWTOs on the wiki</a>.
+</p>
+% endif
+
+% for subject in subjects:
+% if subject in mapping:
+<h2>${subject}</h2>
+<p>${mapping[subject]|n}</p>
+% else:
+<p>Unknown subject '${subject}'.</p>
+% endif
+% endfor
+% if simple:
+<a href="javascript:window.close();">Close</a>
+% endif
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#attr pageclass = 'help'
-
-#def title
-#if len($subjects) == 1
-Help on $subjects[0]
-#else
-Help
-#end if
-#end def
-
-
-#def body
-#if not $simple
-<p>Topics:
-#for $key in sorted($mapping)
-<a href="help?subject=$key">$key</a>
-#end for
-</p>
-<p>
-See also <a href="trac/wiki/tips">tips and HOWTOs on the wiki</a>.
-</p>
-#end if
-#for $subject in $subjects
-#if $subject in $mapping
-<h2>$subject</h2>
-#filter None
-<p>$mapping[$subject]</p>
-#end filter
-#else
-<p>Unknown subject '$subject'.</p>
-#end if
-#end for
-#if $simple
-<a href="javascript:window.close();">Close</a>
-#end if
-#end def
--- /dev/null
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Info on ${machine.name}
+</%def>
+
+<%def name="infoTable()">
+<h2>Info</h2>
+<table>
+ % for key, value in fields:
+ <tr><td>${key}:</td><td>${value}</td></tr>
+ % endfor
+</table>
+</%def>
+
+<%def name="commands()">
+% if on:
+ % if not machine.type.hvm:
+ Console access: type
+ <tt>ssh ${machine.name}@${config.console.hostname}</tt>
+ on Athena. <a href="https://xvm.scripts.mit.edu/wiki/SerialConsole">(more info)</a>
+ % elif has_vnc:
+ <strong><a href="machine/${machine.machine_id}/vnc">Get Console</a></strong>
+ % else:
+ VNC console not enabled; still booting?
+ % endif
+% endif
+<%def name="command_button(title, value, cdrom=False, extra='')">
+<form action="machine/${machine.machine_id}/command/${value}" method="POST">
+ <input type="hidden" name="back" value="info" />
+ <input type="submit" class="button" name="action" value="${title}" ${extra | n}/>
+% if cdrom:
+ Boot CD: ${self.fn.cdromList()}
+% endif
+</form>
+</%def>
+ <div>
+ % if on:
+ ${command_button("Power off", "destroy")}
+ ${command_button("Shutdown", "shutdown")}
+ ${command_button("Reboot", "reboot", cdrom=True)}
+ % else:
+ ${command_button("Power on", "create", cdrom=True)}
+ % endif
+ </div>
+ <div>
+ ${command_button("Delete VM", "delete", extra='''onclick="return confirm('Are you sure that you want to delete this VM?');"''')}
+ </div>
+</form>
+</%def>
+
+<%def name="modifyForm()">
+% if err:
+<p class="error">We had a problem with your request:</p>
+% elif new_machine:
+<p>Successfully modified.</p>
+% endif
+% if on:
+(To edit ram, disk size, or machine name, turn off the machine first.)
+% endif
+<form action="machine/${machine.machine_id}/modify" method="POST">
+ <table>
+ <tr><td>Description:</td><td colspan="2"><textarea name="description" rows="4" cols="60">${defaults.description}</textarea></td></tr>
+ <tr><td>Owner${self.fn.helppopup("Owner")}:</td><td><input type="text" name="owner", value="${defaults.owner}"/></td></tr>
+${self.fn.errorRow('owner', err)}
+ <tr><td>Administrator${self.fn.helppopup("Administrator")}:</td><td><input type="text" name="admin", value="${defaults.administrator}"/></td></tr>
+${self.fn.errorRow('administrator', err)}
+ <tr><td>Contact email:</td><td><input type="text" name="contact" value="${defaults.contact}"/></td></tr>
+${self.fn.errorRow('contact', err)}
+% if not on:
+ <tr><td>Machine Name:</td><td><input type="text" name="name" value="${defaults.name}"/>.${config.dns.domains[0]}</td></tr>
+${self.fn.errorRow('name', err)}
+ <tr>
+ <td>HVM/ParaVM${self.fn.helppopup('HVM/ParaVM')}</td>
+ <td>${self.fn.vmTypeList(defaults.type)}</td>
+ </tr>
+ <tr><td>Ram:</td><td><input type="text" size=3 name="memory" value="${defaults.memory}"/>MiB (max ${max_mem})</td></tr>
+${self.fn.errorRow('memory', err)}
+ <tr><td>Disk:</td><td><input type="text" size=3 name="disksize" value="${defaults.disk}"/>GiB (max ${max_disk})</td><td>WARNING: Modifying disk size may corrupt your data.</td></tr>
+${self.fn.errorRow('disk', err)}
+% else:
+${self.fn.errorRow('name', err)}
+${self.fn.errorRow('memory', err)}
+${self.fn.errorRow('disk', err)}
+% endif
+ <tr><td><input type="submit" class="button" name="action" value="Change"/></td></tr>
+ </table>
+</form>
+</%def>
+
+<div id="info">
+ ${infoTable()}
+</div>
+
+<h2>Commands</h2>
+<div id="commands">
+ ${commands()}
+</div>
+<h2>Settings</h2>
+<div id="modify">
+ ${modifyForm()}
+</div>
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Info on $machine.name
-#end def
-
-#def infoTable()
-<h2>Info</h2>
-<table>
- #for $key, $value in $fields
- <tr><td>$key:</td><td>$value</td></tr>
- #end for
-</table>
-#end def
-
-#def commands()
-#if $on
- #if not $machine.type.hvm
- Console access: type
- <tt>ssh $machine.name@$config.console.hostname</tt>
- on Athena. <a href="https://xvm.scripts.mit.edu/wiki/SerialConsole">(more info)</a>
- #elif $has_vnc
- <strong><a href="vnc?machine_id=$machine.machine_id">Get Console</a></strong>
- #else
- VNC console not enabled; still booting?
- #end if
-#end if
-<form action="command" method="POST">
- <input type="hidden" name="back" value="info"/>
- <input type="hidden" name="machine_id" value="$machine.machine_id"/>
- <div>
- #if $on
- <button type="submit" class="button" name="action" value="Power off">Power off (hard)</button>
- <button type="submit" class="button" name="action" value="Shutdown">Shut down</button>
- <input type="submit" class="button" name="action" value="Reboot"/>
- #else
- <input type="submit" class="button" name="action" value="Power on"/>
- #end if
- </div>
- <div>
- Boot CD:
-#filter None
-$cdromList()#slurp
-#end filter
- </div>
- <div>
- <input type="submit" class="button" name="action" value="Delete VM" onclick="return confirm('Are you sure that you want to delete this VM?');"/>
- </div>
-</form>
-#end def
-
-#def modifyForm()
-#if $err
-<p class="error">We had a problem with your request:</p>
-#else if $varExists('new_machine')
-<p>Successfully modified.</p>
-#end if
-#if $on
-(To edit ram, disk size, or machine name, turn off the machine first.)
-#end if
-<form action="modify" method="POST">
- <input type="hidden" name="machine_id" value="$defaults.machine_id"/>
- <table>
- <tr><td>Description:</td><td colspan="2"><textarea name="description" rows="4" cols="60">$defaults.description</textarea></td></tr>
- <tr><td>Owner#slurp
-#filter None
-$helppopup("Owner")#slurp
-#end filter
-:</td><td><input type="text" name="owner", value="$defaults.owner"/></td></tr>
-#filter None
-$errorRow('owner', $err)
-#end filter
- <tr><td>Administrator#slurp
-#filter None
-$helppopup("Administrator")#slurp
-#end filter
-:</td><td><input type="text" name="admin", value="$defaults.administrator"/></td></tr>
-#filter None
-$errorRow('administrator', $err)
-#end filter
- <tr><td>Contact email:</td><td><input type="text" name="contact" value="$defaults.contact"/></td></tr>
-#filter None
-$errorRow('contact', $err)
-#end filter
-#if not $on
- <tr><td>Machine Name:</td><td><input type="text" name="name" value="$defaults.name"/>.${config.dns.domains[0]}</td></tr>
-#filter None
-$errorRow('name', $err)
-#end filter
- <tr>
- <td>HVM/ParaVM#slurp
-#filter None
-$helppopup('HVM/ParaVM')#slurp
-#end filter
-</td>
- <td>#slurp
-#filter None
-$vmTypeList($defaults.type)#slurp
-#end filter
-</td>
- </tr>
- <tr><td>Ram:</td><td><input type="text" size=3 name="memory" value="$defaults.memory"/>MiB (max $max_mem)</td></tr>
-#filter None
-$errorRow('memory', $err)
-#end filter
- <tr><td>Disk:</td><td><input type="text" size=3 name="disksize" value="$defaults.disk"/>GiB (max $max_disk)</td><td>WARNING: Modifying disk size may corrupt your data.</td></tr>
-#filter None
-$errorRow('disk', $err)
-#end filter
-#else
-#filter None
-$errorRow('name', $err)
-$errorRow('memory', $err)
-$errorRow('disk', $err)
-#end filter
-#end if
- <tr><td><input type="submit" class="button" name="action" value="Change"/></td></tr>
- </table>
-</form>
-#end def
-
-#def body
-<div id="info">
-#filter None
- $infoTable()
-#end filter
-</div>
-
-<h2>Commands</h2>
-<div id="commands">
-#filter None
- $commands()
-#end filter
-</div>
-<h2>Settings</h2>
-<div id="modify">
-#filter None
- $modifyForm()
-#end filter
-</div>
-#end def
--- /dev/null
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Invalid Input
+</%def>
+
+<p>Your input was bad:</p>
+<table>
+<tr><td>Field</td><td>value</td><td>reason</td></tr>
+<tr><td>${err_field}</td><td>${err_value}</td><td>${errorMessage}</td></tr>
+%if stderr:
+<p>Printed to standard error:</p>
+<pre>${stderr}</pre>
+%endif
+</table>
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Invalid Input
-#end def
-
-#def body
-<p>Your input was bad:</p>
-<table>
-<tr><td>operation</td><td>Field</td><td>value</td><td>reason</td></tr>
-<tr><td>$op</td><td>$err_field</td><td>$err_value</td><td>$errorMessage</td></tr>
-#if $stderr
-<p>Printed to standard error:</p>
-<pre>$stderr</pre>
-#end if
-</table>
-#end def
--- /dev/null
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
+<%!
+ import datetime
+%>
+
+
+<%def name="title()">
+VM List
+</%def>
+
+<%def name="createForm()">
+% if cant_add_vm:
+<p>${cant_add_vm}</p>
+% else:
+<h2>Create a new VM</h2>
+% if err:
+<p class="error">We had a problem with your request:</p>
+% elif new_machine:
+<p>Congratulations! You successfully created a new VM called ${new_machine}.</p>
+% endif
+ <form action="create" method="POST">
+ <input type="hidden" name="back" value="list"/>
+ <table>
+ ${self.fn.errorRow('create', err)}
+ <tr>
+ <td>Name</td>
+ <td><input type="text" name="name" value="${defaults.name}"/>.${config.dns.domains[0]}</td>
+ </tr>
+ ${self.fn.errorRow('name', err)}
+ <tr>
+ <td>Description</td>
+ <td><textarea name="description" rows="4" cols="60">${defaults.description}</textarea></td>
+ </tr>
+ ${self.fn.errorRow('description', err)}
+ <tr>
+ <td>Memory</td>
+ <td><input type="text" name="memory" value="${defaults.memory}" size=3/> MiB (${max_memory} max)</td>
+ </tr>
+ ${self.fn.errorRow('memory', err)}
+ <tr>
+ <td>Disk</td>
+ <td><input type="text" name="disksize" value="${defaults.disk}" size=3/> GiB (${"%0.1f" % (max_disk-0.05)} max)</td>
+ </tr>
+ ${self.fn.errorRow('disk', err)}
+ <tr>
+ <td>HVM/ParaVM${self.fn.helppopup('HVM/ParaVM')}</td>
+ <td>
+ ${self.fn.vmTypeList(defaults.type)}
+ </td>
+ </tr>
+ ${self.fn.errorRow('vmtype', err)}
+ ${self.fn.errorRow('autoinstall', err)}
+ <tr>
+ <td>Autoinstall${self.fn.helppopup('Autoinstalls')}</td>
+ <td><input type="radio" name="cd_or_auto" id="cd_or_auto_auto"
+ onchange="\$('cdromlist').value = ''; \$('vmtype-linux').checked = true">
+${self.fn.autoList(defaults.cdrom, "$('cd_or_auto_auto').checked = true;$('cdromlist').value = '';$('vmtype-linux').checked = true")}
+ (experimental; 2-3 minutes, and you have a machine with empty root password.)
+ </input>
+ </tr>
+ <tr>
+ <td>Boot CD</td>
+ <td><input type="radio" name="cd_or_auto" id="cd_or_auto_cd" checked="checked"
+ onchange="\$('autoinstalllist').value = ''; \$('vmtype-linux-hvm').checked = true">
+${self.fn.cdromList(defaults.cdrom, "$('cd_or_auto_cd').checked = true;$('autoinstalllist').value = '';$('vmtype-linux-hvm').checked = true")}
+</td>
+ </input>
+ </tr>
+ ${self.fn.errorRow('cdrom', err)}
+ ${self.fn.errorRow('cdrom', err)}
+ <tr>
+ <td>Owner</td>
+ <td><input type="text" name="owner" value="${defaults.owner}"/></td>
+ </tr>
+ ${self.fn.errorRow('owner', err)}
+ </table>
+ <input type="submit" class="button" value="Create it!"/><br />
+ Windows notes: ${self.fn.helppopup('Windows')}
+ </form>
+% endif
+</%def>
+
+<%def name="machineRow(machine)">
+ <tr>
+ <td rowspan="2">
+ % if machine.uptime and installing[machine]:
+ <img src="static/power_installing.png" alt="Installing..." />
+ % else:
+ <form action="machine/${machine.machine_id}/command/${'shutdown' if machine.uptime else 'create'}" method="post">
+ <input type="hidden" name="back" value="list"/>
+ <input type="hidden" name="machine_id"
+ value="${machine.machine_id}"/>
+<input type="submit" class="power ${'on' if machine.uptime else 'off'}" name="action" value="${'Shutdown' if machine.uptime else 'Power on'}"\
+% if machine.uptime:
+ onclick="return confirm('Are you sure you want to power off this VM?');"
+% endif
+/>
+ </form>
+ % endif
+ </td>
+ <td><a href="machine/${machine.machine_id}">${machine.name}</a></td>
+ <td>${machine.memory}M</td>
+ <td>${machine.owner}</td>
+ <td>${machine.administrator}</td>
+% if machine.nics:
+ <td>${', '.join(nic.ip for nic in machine.nics)}</td>
+% else:
+ <td></td>
+% endif
+<td>\
+% if machine.uptime:
+${datetime.timedelta(seconds=int(machine.uptime))}\
+% endif
+</td>
+ <td>\
+% if has_vnc[machine] == True:
+<a href="machine/${machine.machine_id}/vnc">Console</a>\
+% elif has_vnc[machine] == 'ParaVM':
+ParaVM${self.fn.helppopup("ParaVM Console")}
+% elif has_vnc[machine] != 'Off':
+${has_vnc[machine]}
+% endif
+</td>
+ </tr>
+ <tr>
+ <td colspan="7" style="padding-left: 1em; color: #666">${machine.description|self.fn.module.nl2br}</td>
+ </tr>
+</%def>
+
+<%def name="machineList(machines)">
+ <table cellspacing="0" cellpadding="2">
+ <tr>
+ <th></th>
+ <th>Name</th>
+ <th>Memory</th>
+ <th>Owner${self.fn.helppopup('Owner')}</th>
+ <th>Administrator${self.fn.helppopup('Administrator')}</th>
+ <th>IP</th>
+ <th>Uptime</th>
+ <th>VNC</th>
+ </tr>
+% for machine in machines:
+ ${machineRow(machine)}
+% endfor
+ </table>
+ <script type="text/javascript" src="/static/stripe.js"></script>
+ <script type="text/javascript">
+ document.observe("dom:loaded", function() {
+ stripe(\$('machinelist').getElementsByTagName('table')[0],
+ 'stripedrow');
+ });
+ </script>
+</%def>
+
+<p style="font-size: 125%;"><a href="http://${config.web.hostname}">What is XVM?</a></p>
+% if not machines:
+<p>You don't currently control any VMs.</p>
+% endif
+ <p><a href="list">refresh</a></p>
+ <div id="machinelist">
+ ${machineList(machines)}
+ </div>
+${createForm()}
+++ /dev/null
-#from skeleton import skeleton
-#from invirt.config import structs as config
-#extends skeleton
-#import datetime
-
-
-#def title
-VM List
-#end def
-
-#def createForm()
-#if $cant_add_vm
-<p>$cant_add_vm</p>
-#else
-<h2>Create a new VM</h2>
-#if $err
-<p class="error">We had a problem with your request:</p>
-#else if $varExists('new_machine')
-<p>Congratulations! You successfully created a new VM called $new_machine.</p>
-#end if
- <form action="create" method="POST">
- <input type="hidden" name="back" value="list"/>
- <table>
-#filter None
- $errorRow('create', $err)
-#end filter
- <tr>
- <td>Name</td>
- <td><input type="text" name="name" value="$defaults.name"/>.${config.dns.domains[0]}</td>
- </tr>
-#filter None
-$errorRow('name', $err)
-#end filter
- <tr>
- <td>Description</td>
- <td><textarea name="description" rows="4" cols="60">$defaults.description</textarea></td>
- </tr>
-#filter None
-$errorRow('description', $err)
-#end filter
- <tr>
- <td>Memory</td>
- <td><input type="text" name="memory" value="$defaults.memory" size=3/> MiB ($max_memory max)</td>
- </tr>
-#filter None
-$errorRow('memory', $err)
-#end filter
- <tr>
- <td>Disk</td>
- <td><input type="text" name="disksize" value="$defaults.disk" size=3/> GiB (${"%0.1f" % ($max_disk-0.05)} max)</td>
- </tr>
-#filter None
-$errorRow('disk', $err)
-#end filter
- <tr>
- <td>HVM/ParaVM#slurp
-#filter None
-$helppopup('HVM/ParaVM')#slurp
-#end filter
-</td>
- <td>
-#filter None
-$vmTypeList($defaults.type)
-#end filter
-</td>
- </tr>
-#filter None
-$errorRow('vmtype', $err)
-#end filter
-#filter None
-$errorRow('autoinstall', $err)
-#end filter
- <tr>
- <td>Autoinstall#slurp
-#filter None
-$helppopup('Autoinstalls')#slurp
-#end filter
-</td>
- <td><input type="radio" name="cd_or_auto" id="cd_or_auto_auto"
- onchange="\$('cdromlist').value = ''; \$('vmtype-linux').checked = true">
-#filter None
-$autoList($defaults.cdrom, "$('cd_or_auto_auto').checked = true;$('cdromlist').value = '';$('vmtype-linux').checked = true")
- (experimental; 2-3 minutes, and you have a machine with empty root password.)
-#end filter
- </input>
- </tr>
- <tr>
- <td>Boot CD</td>
- <td><input type="radio" name="cd_or_auto" id="cd_or_auto_cd" checked="checked"
- onchange="\$('autoinstalllist').value = ''; \$('vmtype-linux-hvm').checked = true">
-#filter None
-$cdromList($defaults.cdrom, "$('cd_or_auto_cd').checked = true;$('autoinstalllist').value = '';$('vmtype-linux-hvm').checked = true")
-#end filter
-</td>
- </input>
- </tr>
-$errorRow('cdrom', $err)
-$errorRow('cdrom', $err)
- <tr>
- <td>Owner</td>
- <td><input type="text" name="owner" value="$defaults.owner"/></td>
- </tr>
-#filter None
- $errorRow('owner', $err)
-#end filter
- </table>
- <input type="submit" class="button" value="Create it!"/><br />
- Windows notes: #slurp
-#filter None
-$helppopup('Windows')#slurp
-#end filter
- </form>
-#end if
-#end def
-
-#def machineRow($machine)
- <tr>
- <td rowspan="2">
- <form action="command" method="post">
- <input type="hidden" name="back" value="list"/>
- <input type="hidden" name="machine_id"
- value="$machine.machine_id"/>
-<input type="submit" class="power #slurp
-#if $machine.uptime then 'on' else 'off'
-" name="action" value="#slurp
-#if $machine.uptime then 'Power off' else 'Power on'
-"#slurp
-#if $machine.uptime
- onclick="return confirm('Are you sure you want to power off this VM?');"
-#end if
-/>
- </form>
- </td>
- <td><a href="info?machine_id=$machine.machine_id">$machine.name</a></td>
- <td>${machine.memory}M</td>
- <td>$machine.owner</td>
- <td>$machine.administrator</td>
-#if $machine.nics
-#set $nic = $machine.nics[0]
- <td>$nic.ip</td>
-#else
- <td></td>
-#end if
-<td>#slurp
-#if $machine.uptime
-${datetime.timedelta(seconds=int(machine.uptime))}#slurp
-#end if
-</td>
- <td>#slurp
-#if $has_vnc[$machine] == True
-<a href="vnc?machine_id=$machine.machine_id">Console</a>#slurp
-#else if $has_vnc[$machine] != 'Off'
-#filter None
-$has_vnc[$machine]
-#end filter
-#end if
-</td>
- </tr>
- <tr>
- <td colspan="7" style="padding-left: 1em; color: #666">$machine.description</td>
- </tr>
-#end def
-
-#def machineList($machines)
- <table cellspacing="0" cellpadding="2">
- <tr>
- <th></th>
- <th>Name</th>
- <th>Memory</th>
- <th>Owner#slurp
-#filter None
-$helppopup('Owner')#slurp
-#end filter
-</th>
- <th>Administrator#slurp
-#filter None
-$helppopup('Administrator')#slurp
-#end filter
-</th>
- <th>IP</th>
- <th>Uptime</th>
- <th>VNC</th>
- </tr>
- #for $machine in $machines:
- #filter None
- $machineRow($machine)
- #end filter
- #end for
- </table>
- <script type="text/javascript" src="/static/stripe.js"></script>
- <script type="text/javascript">
- document.observe("dom:loaded", function() {
- stripe(\$('machinelist').getElementsByTagName('table')[0],
- 'stripedrow');
- });
- </script>
-#end def
-
-
-#def body
-<p style="font-size: 125%;"><a href="http://${config.web.hostname}">What is XVM?</a></p>
-#if not $machines
-<p>You don't currently control any VMs.</p>
-#end if
- <p><a href="list">refresh</a></p>
- <div id="machinelist">
- #filter None
- $machineList($machines)
- #end filter
- </div>
-#filter None
-$createForm()
-#end filter
-#end def
--- /dev/null
+<%page expression_filter="h"/>
+<%namespace name="fn" file="functions.mako" inheritable="True"/>
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head><title>${self.title()} — XVM</title>
+ <base href="${cherrypy.request.base}${"/admin/" if cherrypy.request.state.isadmin else ""}" />
+ <link href="/static/favicon.ico" type="image/x-icon" rel="shortcut icon">
+ <link rel="stylesheet" href="/static/style.css" type="text/css" />
+ <link rel="stylesheet" href="/static/layout.css" type="text/css" media="screen" />
+ <script type="text/javascript" src="/static/prototype.js"></script>
+ <script type="text/javascript">
+var helpWin = null;
+function closeWin(){
+ if (helpWin != null){
+ if(!helpWin.closed)
+ helpWin.close();
+ }
+}
+
+function helppopup(name){
+ closeWin()
+ helpWin = window.open("help?simple=true&subject="+encodeURIComponent(name), "Help",
+"status, height = 300, width = 400");
+ if (window.focus){helpWin.focus();}
+ return false;
+}
+</script>
+</head>
+<body id="body"
+% if hasattr(self.attr, 'pageclass'):
+ class="${self.attr.pageclass}"
+% endif
+ >
+
+% if False:
+<div>
+<p>We are in the process of modifying the service. Things likely will not work.</p>
+</div>
+% endif
+
+% if error_text is not UNDEFINED:
+<div id="err">
+<p>STDERR:</p><pre>${error_text}</pre>
+</div>
+% endif
+
+% if not simple:
+% if cherrypy.request.login:
+<p class="loggedin">Welcome, <span class="name">${cherrypy.request.login}</span>.
+% if cherrypy.request.state.isadmin:
+You are currently authenticated as an administrator.
+% endif
+</p>
+% endif
+
+<ul class="navigation">
+<li><a href="list">List</a></li>
+% if machine:
+<li><a href="machine/${machine.machine_id}">Info</a></li>
+<li><a href="machine/${machine.machine_id}/vnc">Console</a></li>
+% endif
+<li><a href="help">Help</a></li>
+</ul>
+
+% endif
+
+<div id="result" class="result">
+% if result:
+${result}
+% endif
+</div>
+
+% if not simple:
+<h1>${self.title()} — XVM</h1>
+% endif
+${next.body()}
+% if not simple:
+<hr />
+Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
+% endif
+</body>
+</html>
+
+<%def name="title()">
+XVM
+</%def>
+++ /dev/null
-#from functions import functions
-#extends functions
-
-#def full_body
-<!DOCTYPE html
-PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html>
-<head><title>$title — XVM</title>
- <link href="static/favicon.ico" type="image/x-icon" rel="shortcut icon">
- <link rel="stylesheet" href="static/style.css" type="text/css" />
- <link rel="stylesheet" href="static/layout.css" type="text/css" media="screen" />
- <script type="text/javascript" src="static/prototype.js"></script>
- <script type="text/javascript">
-var helpWin = null;
-function closeWin(){
- if (helpWin != null){
- if(!helpWin.closed)
- helpWin.close();
- }
-}
-
-function helppopup(name){
- closeWin()
- helpWin = window.open("help?simple=true&subject="+encodeURIComponent(name), "Help",
-"status, height = 300, width = 400");
- if (window.focus){helpWin.focus();}
- return false;
-}
-</script>
-</head>
-<body id="body"
-#if hasattr($self, 'pageclass'):
- class="$pageclass"
-#end if
- >
-
-#if False
-<div>
-<p>We are in the process of modifying the service. Things likely will not work.</p>
-</div>
-#end if
-
-<div id="err">
-#if $varExists('error_text')
-<p>STDERR:</p><pre>$error_text</pre>
-#end if
-</div>
-
-#if not $varExists('simple') or not $simple
-<p class="loggedin">Welcome, <span class="name">$user</span>.</p>
-
-<ul class="navigation">
-<li><a href="list">List</a></li>
-#if $varExists('machine')
-<li><a href="info?machine_id=$machine.machine_id">Info</a></li>
-<li><a href="vnc?machine_id=$machine.machine_id">Console</a></li>
-#end if
-<li><a href="help">Help</a></li>
-</ul>
-#end if
-<div id="result" class="result">
-#if $varExists('result')
-$result
-#end if
-</div>
-
-#if not $varExists('simple') or not $simple
-<h1>$title — XVM</h1>
-#end if
-#filter None
-$body
-#end filter
-#if not $varExists('simple') or not $simple
-<hr />
-Questions? Contact <a href="mailto:xvm@mit.edu">xvm@mit.edu</a>.
-#end if
-</body>
-</html>
-#end def
-#from skeleton import skeleton
-#extends skeleton
+<%page expression_filter="h"/>
+<%inherit file="skeleton.mako" />
-
-#def title
+<%def name="title()">
Intro
-#end def
+</%def>
-#def body
<h1>XVM — Virtual Servers for MIT </h1>
<p><strong>xvm.mit.edu</strong> is a virtualization service for the
<p>MIT users:</p>
<blockquote><big><a
-href="https://$hostname/"><strong><font color="green">→</font> Log in to XVM now</strong>
+href="https://${config.web.hostname}/"><strong><font color="green">→</font> Log in to XVM now</strong>
</a></big></blockquote>
<p>You'll need an <a href="http://ca.mit.edu/">MIT personal
certificate</a>.</p>
<p>Questions and feedback welcome at <a
href="mailto:xvm@mit.edu">xvm@mit.edu</a>.</p>
-
-#end def
--- /dev/null
+<%page expression_filter="h" />
+<%inherit file="skeleton.mako" />
+
+<%def name="title()">
+Console to ${machine.name}
+</%def>
+
+<style type='text/css'>body { max-width: none }</style>
+% if not on:
+<p> Your machine appears to be off.</p>
+% elif not has_vnc:
+<p> Your machine appears to not be accepting VNC connections. Perhaps you have a ParaVM machine?</p>
+% endif
+
+<p>See <a href="help?subject=Console" target="_blank">tips</a> about framebuffer and other issues.</p>
+<applet code="VncViewer.class" archive="https://${hostname}:446/static/VncViewer.jar"
+ width="100%" height="1000">
+<param name="PORT" value="${port}">
+<param name="HOST" value="${hostname}">
+<param name="VMNAME" value="${machine.name}">
+<param name="AUTHTOKEN" value="${authtoken}">
+<param name="SocketFactory" value="VNCProxyConnectSocketFactory">
+</applet>
+++ /dev/null
-#from skeleton import skeleton
-#extends skeleton
-
-#def title
-Console to $machine.name
-#end def
-
-#def body
-<style type='text/css'>body { max-width: none }</style>
-#if not $on
-<p> Your machine appears to be off.</p>
-#else if not $has_vnc
-<p> Your machine appears to not be accepting VNC connections. Perhaps you have a ParaVM machine?</p>
-#end if
-<p>See <a href="help?subject=Console" target="_blank">tips</a> about framebuffer and other issues.</p>
-<APPLET CODE="VncViewer.class" ARCHIVE="https://$hostname:446/static/VncViewer.jar"
- WIDTH="100%" HEIGHT="1000">
-<PARAM NAME="PASSWORD" VALUE="moocow">
-<PARAM NAME="PORT" VALUE="$port">
-<PARAM NAME="HOST" VALUE="$hostname">
-<PARAM NAME="VMNAME" VALUE="$machine.name">
-<PARAM NAME="AUTHTOKEN" VALUE="$authtoken">
-<PARAM NAME="SocketFactory" VALUE="VNCProxyConnectSocketFactory">
-</APPLET>
-#end def
--- /dev/null
+invirt.fcgi
\ No newline at end of file
raise InvalidInput('contact', contact, "Not a valid email.")
return contact
-def testDisk(user, disksize, machine=None):
- return disksize
-
def testName(user, name, machine=None):
if name is None:
return None
--- /dev/null
+import os, sys
+
+import cherrypy
+from mako.template import Template
+from mako.lookup import TemplateLookup
+import simplejson
+import datetime, decimal
+from StringIO import StringIO
+from invirt.config import structs as config
+from webcommon import State
+
+
+class MakoHandler(cherrypy.dispatch.LateParamPageHandler):
+ """Callable which processes a dictionary, returning the rendered body."""
+
+ def __init__(self, template, next_handler,
+ content_type='text/html; charset=utf-8'):
+ self.template = template
+ self.next_handler = next_handler
+ self.content_type = content_type
+
+ def __call__(self):
+ env = globals().copy()
+ env.update(self.next_handler())
+ cherrypy.response.headers['Content-Type'] = self.content_type
+ return self.template.render(**env)
+
+
+class MakoLoader(object):
+
+ def __init__(self):
+ self.lookups = {}
+
+ def get_lookup(self, directories, module_directory=None,
+ collection_size=-1, imports=[], **kwargs):
+ # Find the appropriate template lookup.
+ key = (tuple(directories), module_directory)
+ try:
+ lookup = self.lookups[key]
+ except KeyError:
+ lookup = TemplateLookup(directories=directories,
+ module_directory=module_directory,
+ collection_size=collection_size,
+ default_filters=['decode.utf8'],
+ input_encoding='utf-8',
+ output_encoding='utf-8',
+ imports=imports,
+ )
+ self.lookups[key] = lookup
+ return lookup
+
+ def __call__(self, filename, directories, module_directory=None,
+ collection_size=-1, content_type='text/html; charset=utf-8',
+ imports=[]):
+ cherrypy.request.lookup = lookup = self.get_lookup(
+ directories, module_directory, collection_size, imports)
+ cherrypy.request.template = t = lookup.get_template(filename)
+ cherrypy.request.handler = MakoHandler(
+ t, cherrypy.request.handler, content_type)
+
+cherrypy.tools.mako = cherrypy.Tool('on_start_resource', MakoLoader())
+
+
+def revertStandardError():
+ """Move stderr to stdout, and return the contents of the old stderr."""
+ errio = sys.stderr
+ if not isinstance(errio, StringIO):
+ return ''
+ sys.stderr = sys.stdout
+ errio.seek(0)
+ return errio.read()
+
+
+def catchStderr():
+ old_handler = cherrypy.request.handler
+ def wrapper(*args, **kwargs):
+ sys.stderr = StringIO()
+ ret = old_handler(*args, **kwargs)
+ e = revertStandardError()
+ if e:
+ if isinstance(ret, dict):
+ ret["error_text"] = e
+ return ret
+ if old_handler:
+ cherrypy.request.handler = wrapper
+
+cherrypy.tools.catch_stderr = cherrypy.Tool('before_handler', catchStderr)
+
+
+class JSONEncoder(simplejson.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, datetime.datetime):
+ return str(obj)
+ elif isinstance(obj, decimal.Decimal):
+ return float(obj)
+ else:
+ return simplejson.JSONEncoder.default(self, obj)
+
+
+def jsonify_tool_callback(*args, **kwargs):
+ if not cherrypy.request.cached:
+ response = cherrypy.response
+ response.headers['Content-Type'] = 'text/javascript'
+ response.body = JSONEncoder().iterencode(response.body)
+
+cherrypy.tools.jsonify = cherrypy.Tool('before_finalize',
+ jsonify_tool_callback, priority=30)
+
+
+def require_login():
+ """If the user isn't logged in, raise 403 with an error."""
+ if cherrypy.request.login is False:
+ raise cherrypy.HTTPError(403,
+ "You are not authorized to access that resource")
+
+cherrypy.tools.require_login = cherrypy.Tool('on_start_resource',
+ require_login, priority=150)
+
+
+def require_POST():
+ """If the request isn't a POST request, raise 405 Method Not Allowed"""
+ if cherrypy.request.method != "POST":
+ raise cherrypy.HTTPError(405,
+ "You must submit this request with POST")
+
+cherrypy.tools.require_POST = cherrypy.Tool('on_start_resource',
+ require_POST, priority=150)
+
+
+def remote_user_login():
+ """Get remote user from SSL or GSSAPI, and store in request object.
+
+Get the current user based on environment variables set by SSL or
+GSSAPI, and store it in the attribute cherrpy.request.login.
+
+Per the CherryPy API (http://www.cherrypy.org/wiki/RequestObject#login),
+the attribute is set to the username on successful login, to False on
+failed login, and is left at None if the user attempted no authentication.
+"""
+ environ = cherrypy.request.wsgi_environ
+ user = environ.get('REMOTE_USER')
+ if user is None:
+ return
+ if environ.get('AUTH_TYPE') == 'Negotiate':
+ # Convert the krb5 principal into a krb4 username
+ if not user.endswith('@%s' % config.kerberos.realm):
+ cherrypy.request.login = False # failed to log in
+ else:
+ cherrypy.request.login = user.split('@')[0].replace('/', '.')
+ else:
+ cherrypy.request.login = user
+
+cherrypy.tools.remote_user_login = cherrypy.Tool('on_start_resource',
+ remote_user_login, priority=50)
+
+
+def invirtwebstate_init():
+ """Initialize the cherrypy.request.state object from Invirt"""
+ if not hasattr(cherrypy.request, "state"):
+ cherrypy.request.state = State(cherrypy.request.login)
+
+cherrypy.tools.invirtwebstate = cherrypy.Tool('on_start_resource',
+ invirtwebstate_init, priority=100)
+
+
+class View(object):
+ _cp_config = {'tools.mako.directories':
+ [os.path.join(os.path.dirname(__file__),'templates')]}
+invirt-web (0.1.0) unstable; urgency=low
+
+ [Quentin Smith]
+ * Switch to CherryPy in place of our home-grown web framework.
+ * Switch from the Cheetah templating engine to the Mako templating engine.
+ * New URI scheme: /machine/<numeric-id>/<operation>
+ rather than /<operation>?machine_id=<numeric-id> .
+ * Fix power-on/power-off/reboot buttons for IE <=8.
+ * Move some bits of presentation code from Python into templates.
+ * Clarify that Windows licenses are available from MIT for staff.
+
+ [Evan Broder]
+ * Show newlines from descriptions in list page.
+ * Only aklog to a cell if encryption is actually needed.
+ * Re-arrange the authz configuration.
+
+ -- Greg Price <price@mit.edu> Sat, 19 Dec 2009 21:53:40 -0500
+
invirt-web (0.0.24) unstable; urgency=low
* Update authorization code for new config structure.
libapache2-mod-auth-sslcert, libapache2-mod-auth-kerb,
debathena-ssl-certificates,
# python libraries
- python-flup, python-cheetah, python-simplejson,
- python-dns, python-dnspython,
+ python-flup, python-simplejson,
+ python-dns, python-dnspython, python-cherrypy3,
+ python-mako,
# misc
kstart,
debathena-afs-config, openafs-modules-xen,
On Debian systems, the complete text of the GNU General Public License
can be found in the file /usr/share/common-licenses/GPL.
+
+The file "code/static/power_installing.png" is from the Human-O2 icon
+set by Oliver Scholtz and is released under the "GNU/GPL" (source:
+http://www.iconfinder.net/icondetails/24350/128/ -
+http://schollidesign.deviantart.com/art/Human-O2-Iconset-105344123)
RewriteRule ^/admin/static(.*) /static/$1 [L]
RewriteRule ^/trac(.*) ${tracuri}$1 [R,L]
RewriteRule ^/invirt - [L]
- RewriteRule ^/sipb-xen(.*) /invirt$1 [PT]
RewriteRule ^/kill.cgi - [L]
- RewriteRule ^/~ - [L]
- RewriteRule ^/(.*) /var/www/invirt-web/main.fcgi/$1 [L]
+ RewriteRule ^/(.*) /var/www/invirt-web/unauth.fcgi/$1 [L]
ErrorLog /var/log/apache2/error.log
RewriteRule ^/admin/static(.*) /static/$1 [L]
RewriteRule ^/trac(.*) ${tracuri}$1 [R,L]
RewriteRule ^/kill.cgi - [L]
- RewriteRule ^/~ - [L]
- RewriteRule ^/(.*) /var/www/invirt-web/main.fcgi/$1 [L]
+ RewriteRule ^/(.*) /var/www/invirt-web/auth.fcgi/$1 [L]
RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 0