Get username and state from CherryPy request
[invirt/packages/invirt-web.git] / code / main.py
index 2875c16..fd95e84 100755 (executable)
@@ -12,8 +12,9 @@ import simplejson
 import sys
 import time
 import urllib
 import sys
 import time
 import urllib
+import socket
+import cherrypy
 from StringIO import StringIO
 from StringIO import StringIO
-
 def revertStandardError():
     """Move stderr to stdout, and return the contents of the old stderr."""
     errio = sys.stderr
 def revertStandardError():
     """Move stderr to stdout, and return the contents of the old stderr."""
     errio = sys.stderr
@@ -44,6 +45,31 @@ from invirt.database import Machine, CDROM, session, connect, MachineAccess, Typ
 from invirt.config import structs as config
 from invirt.common import InvalidInput, CodeError
 
 from invirt.config import structs as config
 from invirt.common import InvalidInput, CodeError
 
+from view import View
+
+class InvirtWeb(View):
+    def __init__(self):
+        super(self.__class__,self).__init__()
+        connect()
+        self._cp_config['tools.require_login.on'] = True
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/list.mako")
+    def list(self):
+        """Handler for list requests."""
+        checkpoint.checkpoint('Getting list dict')
+        d = getListDict(cherrypy.request.login, cherrypy.request.state)
+        checkpoint.checkpoint('Got list dict')
+        return d
+    index=list
+
+    @cherrypy.expose
+    @cherrypy.tools.mako(filename="/helloworld.mako")
+    def helloworld(self):
+        return {}
+        return "Hello world!\nYour request: "+repr(dir(cherrypy.request))
+    helloworld._cp_config['tools.require_login.on'] = False
+
 def pathSplit(path):
     if path.startswith('/'):
         path = path[1:]
 def pathSplit(path):
     if path.startswith('/'):
         path = path[1:]
@@ -121,7 +147,7 @@ class Defaults:
         if max_memory is not None:
             self.memory = min(self.memory, max_memory)
         if max_disk is not None:
         if max_memory is not None:
             self.memory = min(self.memory, max_memory)
         if max_disk is not None:
-            self.max_disk = min(self.disk, max_disk)
+            self.disk = min(self.disk, max_disk)
         for key in kws:
             setattr(self, key, kws[key])
 
         for key in kws:
             setattr(self, key, kws[key])
 
@@ -150,7 +176,7 @@ 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,
     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=validate.vmtype,
+                disksize=validate.disksize, owner=validate.owner, machine_type=getattr(validate, 'vmtype', Defaults.type),
                 cdrom=getattr(validate, 'cdrom', None),
                 autoinstall=getattr(validate, 'autoinstall', None))
 
                 cdrom=getattr(validate, 'cdrom', None),
                 autoinstall=getattr(validate, 'autoinstall', None))
 
@@ -201,8 +227,7 @@ def getListDict(username, state):
     checkpoint.checkpoint('Got max mem/disk')
     defaults = Defaults(max_memory=max_memory,
                         max_disk=max_disk,
     checkpoint.checkpoint('Got max mem/disk')
     defaults = Defaults(max_memory=max_memory,
                         max_disk=max_disk,
-                        owner=username,
-                        cdrom='gutsy-i386')
+                        owner=username)
     checkpoint.checkpoint('Got defaults')
     def sortkey(machine):
         return (machine.owner != username, machine.owner, machine.name)
     checkpoint.checkpoint('Got defaults')
     def sortkey(machine):
         return (machine.owner != username, machine.owner, machine.name)
@@ -217,13 +242,6 @@ def getListDict(username, state):
              can_clone=can_clone)
     return d
 
              can_clone=can_clone)
     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.
 
 def vnc(username, state, path, fields):
     """VNC applet page.
 
@@ -270,13 +288,16 @@ def getHostname(nic):
 
     XXX this should be merged with the similar logic in DNS and DHCP.
     """
 
     XXX this should be merged with the similar logic in DNS and DHCP.
     """
-    if nic.hostname and '.' in nic.hostname:
-        return nic.hostname
+    if nic.hostname:
+        hostname = nic.hostname
     elif nic.machine:
     elif nic.machine:
-        return nic.machine.name + '.' + config.dns.domains[0]
+        hostname = nic.machine.name
     else:
         return None
     else:
         return None
-
+    if '.' in hostname:
+        return hostname
+    else:
+        return hostname + '.' + config.dns.domains[0]
 
 def getNicInfo(data_dict, machine):
     """Helper function for info, get data on nics for a machine.
 
 def getNicInfo(data_dict, machine):
     """Helper function for info, get data on nics for a machine.
@@ -292,8 +313,7 @@ def getNicInfo(data_dict, machine):
     nic_fields = []
     for i in range(len(machine.nics)):
         nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
     nic_fields = []
     for i in range(len(machine.nics)):
         nic_fields.extend([(x % i, y % i) for x, y in nic_fields_template])
-        if not i:
-            data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
+        data_dict['nic%s_hostname' % i] = getHostname(machine.nics[i])
         data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
         data_dict['nic%s_ip' % i] = machine.nics[i].ip
     if len(machine.nics) == 1:
         data_dict['nic%s_mac' % i] = machine.nics[i].mac_addr
         data_dict['nic%s_ip' % i] = machine.nics[i].ip
     if len(machine.nics) == 1:
@@ -378,6 +398,9 @@ def modifyDict(username, state, fields):
             update_acl = True
         if hasattr(validate, 'name'):
             machine.name = validate.name
             update_acl = True
         if hasattr(validate, 'name'):
             machine.name = validate.name
+            for n in machine.nics:
+                if n.hostname == oldname:
+                    n.hostname = validate.name
         if hasattr(validate, 'description'):
             machine.description = validate.description
         if hasattr(validate, 'admin') and validate.admin != machine.administrator:
         if hasattr(validate, 'description'):
             machine.description = validate.description
         if hasattr(validate, 'admin') and validate.admin != machine.administrator:
@@ -431,11 +454,13 @@ def helpHandler(username, state, path, fields):
 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>
 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 and no sshd.</p>
+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're
-quick in logging into the serial console, you can see it running.
+<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
 """,
                     'ParaVM Console': """
 ParaVM machines do not support local console access over VNC.  To
@@ -444,8 +469,19 @@ 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
 username.""" % config.console.hostname,
                     'HVM/ParaVM': """
 HVM machines use the virtualization features of the processor, while
-ParaVM machines use Xen's emulation of virtualization features.  You
-want an HVM virtualized machine.""",
+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': """
                     'CPU Weight': """
 Don't ask us!  We're as mystified as you are.""",
                     'Owner': """
@@ -462,7 +498,7 @@ 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
 group.""",
                     'Quotas': """
 Quotas are determined on a per-locker basis.  Each locker may have a
-maximum of 512 megabytes of active ram, 50 gigabytes of disk, and 4
+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
 active machines.""",
                     'Console': """
 <strong>Framebuffer:</strong> At a Linux boot prompt in your VM, try
@@ -471,7 +507,7 @@ your machine will run just fine, but the applet's display of the
 console will suffer artifacts.
 """,
                     'Windows': """
 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 req    uires 512 MB RAM and at least 7.5 GB disk space (15 GB or more recommended).<br>
+<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.
 """
                     }
 <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.
 """
                     }
@@ -509,8 +545,6 @@ def infoDict(username, state, machine):
         cpu_time_float = float(main_status.get('cpu_time', 0))
         cputime = datetime.timedelta(seconds=int(cpu_time_float))
     checkpoint.checkpoint('Status')
         cpu_time_float = float(main_status.get('cpu_time', 0))
         cputime = datetime.timedelta(seconds=int(cpu_time_float))
     checkpoint.checkpoint('Status')
-    display_fields = """name uptime memory state cpu_weight on_reboot 
-     on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
     display_fields = [('name', 'Name'),
                       ('description', 'Description'),
                       ('owner', 'Owner'),
     display_fields = [('name', 'Name'),
                       ('description', 'Description'),
                       ('owner', 'Owner'),
@@ -525,12 +559,6 @@ def infoDict(username, state, machine):
                       'DISK_INFO',
                       ('state', 'state (xen format)'),
                       ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
                       'DISK_INFO',
                       ('state', 'state (xen format)'),
                       ('cpu_weight', 'CPU weight'+helppopup('CPU Weight')),
-                      ('on_reboot', 'Action on VM reboot'),
-                      ('on_poweroff', 'Action on VM poweroff'),
-                      ('on_crash', 'Action on VM crash'),
-                      ('on_xend_start', 'Action on Xen start'),
-                      ('on_xend_stop', 'Action on Xen stop'),
-                      ('bootloader', 'Bootloader options'),
                       ]
     fields = []
     machine_info = {}
                       ]
     fields = []
     machine_info = {}
@@ -597,16 +625,17 @@ def info(username, state, path, fields):
 
 def unauthFront(_, _2, _3, fields):
     """Information for unauth'd users."""
 
 def unauthFront(_, _2, _3, fields):
     """Information for unauth'd users."""
-    return templates.unauth(searchList=[{'simple' : True}])
+    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.")
 
 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.web.adminacl, 'athena.mit.edu'):
+    if not username in getAfsGroupMembers(config.adminacl, 'athena.mit.edu'):
         raise InvalidInput('username', username,
         raise InvalidInput('username', username,
-                           'Not in admin group %s.' % config.web.adminacl)
+                           'Not in admin group %s.' % config.adminacl)
     newstate = State(username, isadmin=True)
     newstate.environ = state.environ
     return handler(username, newstate, path, fields)
     newstate = State(username, isadmin=True)
     newstate.environ = state.environ
     return handler(username, newstate, path, fields)
@@ -615,7 +644,7 @@ def throwError(_, __, ___, ____):
     """Throw an error, to test the error-tracing mechanisms."""
     raise RuntimeError("test of the emergency broadcast system")
 
     """Throw an error, to test the error-tracing mechanisms."""
     raise RuntimeError("test of the emergency broadcast system")
 
-mapping = dict(list=listVms,
+mapping = dict(#list=listVms,
                vnc=vnc,
                command=command,
                modify=modify,
                vnc=vnc,
                command=command,
                modify=modify,
@@ -643,7 +672,8 @@ Subject: %s
 
 %s
 """ % (to, config.web.hostname, subject, body)
 
 %s
 """ % (to, config.web.hostname, subject, body)
-    p = subprocess.Popen(['/usr/sbin/sendmail', to], stdin=subprocess.PIPE)
+    p = subprocess.Popen(['/usr/sbin/sendmail', '-f', to, to],
+                         stdin=subprocess.PIPE)
     p.stdin.write(mail)
     p.stdin.close()
     p.wait()
     p.stdin.write(mail)
     p.stdin.close()
     p.wait()
@@ -668,7 +698,7 @@ def getUser(environ):
     
     if environ.get('AUTH_TYPE') == 'Negotiate':
         # Convert the krb5 principal into a krb4 username
     
     if environ.get('AUTH_TYPE') == 'Negotiate':
         # Convert the krb5 principal into a krb4 username
-        if not user.endswith('@%s' % config.authn[0].realm):
+        if not user.endswith('@%s' % config.kerberos.realm):
             return
         else:
             return user.split('@')[0].replace('/', '.')
             return
         else:
             return user.split('@')[0].replace('/', '.')