X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-web.git/blobdiff_plain/77238c2233002aa1e8256d2eda37ff65dcd5c916..87b47a1da6fb8cb3a203f1e906cd9066ec335cae:/code/main.py?ds=inline diff --git a/code/main.py b/code/main.py index 0a9342a..7ab52b5 100755 --- a/code/main.py +++ b/code/main.py @@ -52,24 +52,229 @@ class InvirtWeb(View): super(self.__class__,self).__init__() connect() self._cp_config['tools.require_login.on'] = True + self._cp_config['tools.mako.imports'] = ['from invirt.config import structs as config', + 'from invirt import database'] + @cherrypy.expose @cherrypy.tools.mako(filename="/list.mako") - def list(self, username): + def list(self, result=None): """Handler for list requests.""" checkpoint.checkpoint('Getting list dict') - d = getListDict(username, state) + d = getListDict(cherrypy.request.login, cherrypy.request.state) + if result is not None: + d['result'] = result checkpoint.checkpoint('Got list dict') - return templates.list(searchList=[d]) + 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 serial console server +with your Kerberos tickets; there is no root password so sshd will +refuse login.
+ +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.get(kw)) for kw in 'name description owner memory disksize vmtype cdrom autoinstall'.split()])
+ 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 in fields.keys():
+ setattr(d['defaults'], field, fields.get(field))
+ else:
+ d['new_machine'] = parsed_fields['name']
+ return d
+
+ @cherrypy.expose
@cherrypy.tools.mako(filename="/helloworld.mako")
- def helloworld(self):
- return {}
- return "Hello world!\nYour request: "+repr(dir(cherrypy.request))
+ def helloworld(self, **kwargs):
+ return {'request': cherrypy.request, 'kwargs': kwargs}
helloworld._cp_config['tools.require_login.on'] = False
+ class MachineView(View):
+ # This is hairy. Fix when CherryPy 3.2 is out. (rename to
+ # _cp_dispatch, and parse the argument as a list instead of
+ # string
+
+ def __getattr__(self, name):
+ try:
+ machine_id = int(name)
+ cherrypy.request.params['machine_id'] = machine_id
+ 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)
+ checkpoint.checkpoint('Got infodict')
+ return d
+ index = info
+
+ @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', None)
+ try:
+ d = controls.commandResult(cherrypy.request.login, cherrypy.request.state, command_name, machine_id, kwargs)
+ 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 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()
+
def pathSplit(path):
if path.startswith('/'):
path = path[1:]
@@ -93,16 +298,6 @@ class Checkpoint:
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 ('(?)')
-
def makeErrorPre(old, addition):
if addition is None:
return
@@ -113,7 +308,6 @@ def makeErrorPre(old, addition):
Template.database = database
Template.config = config
-Template.helppopup = staticmethod(helppopup)
Template.err = None
class JsonDict:
@@ -172,33 +366,6 @@ def hasVnc(status):
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."""
@@ -221,7 +388,7 @@ def getListDict(username, state):
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')
@@ -242,47 +409,6 @@ def getListDict(username, state):
can_clone=can_clone)
return 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.
@@ -336,35 +462,6 @@ def getDiskInfo(data_dict, machine):
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):
"""Modify a machine as specified by CGI arguments.
@@ -443,86 +540,6 @@ def modify(username, state, path, fields):
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 serial console server
-with your Kerberos tickets; there is no root password so sshd will
-refuse login.
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.
-"""
- }
-
- 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.')
@@ -558,7 +575,6 @@ def infoDict(username, state, machine):
('memory', 'RAM'),
'DISK_INFO',
('state', 'state (xen format)'),
- ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
]
fields = []
machine_info = {}
@@ -612,17 +628,9 @@ def infoDict(username, state, 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,
@@ -644,13 +652,8 @@ 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,
+mapping = dict(
modify=modify,
- info=info,
- create=create,
- help=helpHandler,
unauth=unauthFront,
admin=admin,
overlord=admin,
@@ -690,21 +693,6 @@ def show_error(op, username, fields, err, emsg, traceback):
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: