Disable image cloning when ice3 is booted - this should work as a locking
[invirt/packages/invirt-web.git] / code / main.py
index c419f32..60c2818 100755 (executable)
@@ -11,6 +11,7 @@ import sha
 import simplejson
 import sys
 import time
 import simplejson
 import sys
 import time
+import urllib
 from StringIO import StringIO
 
 def revertStandardError():
 from StringIO import StringIO
 
 def revertStandardError():
@@ -36,8 +37,10 @@ sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
 
 import templates
 from Cheetah.Template import Template
 
 import templates
 from Cheetah.Template import Template
-from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess
+import sipb_xen_database
+from sipb_xen_database import Machine, CDROM, ctx, connect, MachineAccess, Type, Autoinstall
 import validation
 import validation
+import cache_acls
 from webcommon import InvalidInput, CodeError, g
 import controls
 
 from webcommon import InvalidInput, CodeError, g
 import controls
 
@@ -56,12 +59,15 @@ class Checkpoint:
 
 checkpoint = 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"""
 
 def helppopup(subj):
     """Return HTML code for a (?) link to a specified help topic"""
-    return ('<span class="helplink"><a href="help?subject=' + subj + 
-            '&amp;simple=true" target="_blank" ' + 
-            'onclick="return helppopup(\'' + subj + '\')">(?)</a></span>')
+    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:
 
 def makeErrorPre(old, addition):
     if addition is None:
@@ -71,6 +77,7 @@ def makeErrorPre(old, addition):
     else:
         return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
 
     else:
         return '<p>STDERR:</p><pre>' + str(addition) + '</pre>'
 
+Template.sipb_xen_database = sipb_xen_database
 Template.helppopup = staticmethod(helppopup)
 Template.err = None
 
 Template.helppopup = staticmethod(helppopup)
 Template.err = None
 
@@ -96,8 +103,10 @@ class Defaults:
     memory = 256
     disk = 4.0
     cdrom = ''
     memory = 256
     disk = 4.0
     cdrom = ''
+    autoinstall = ''
     name = ''
     name = ''
-    vmtype = 'hvm'
+    type = 'linux-hvm'
+
     def __init__(self, max_memory=None, max_disk=None, **kws):
         if max_memory is not None:
             self.memory = min(self.memory, max_memory)
     def __init__(self, max_memory=None, max_disk=None, **kws):
         if max_memory is not None:
             self.memory = min(self.memory, max_memory)
@@ -136,25 +145,23 @@ def hasVnc(status):
 def parseCreate(user, fields):
     name = fields.getfirst('name')
     if not validation.validMachineName(name):
 def parseCreate(user, fields):
     name = fields.getfirst('name')
     if not validation.validMachineName(name):
-        raise InvalidInput('name', name, 'You must provide a machine name.')
+        raise InvalidInput('name', name, 'You must provide a machine name.  Max 22 chars, alnum plus \'-\' and \'_\'.')
     name = name.lower()
 
     if Machine.get_by(name=name):
         raise InvalidInput('name', name,
                            "Name already exists.")
     name = name.lower()
 
     if Machine.get_by(name=name):
         raise InvalidInput('name', name,
                            "Name already exists.")
-    
+
     owner = validation.testOwner(user, fields.getfirst('owner'))
 
     memory = fields.getfirst('memory')
     memory = validation.validMemory(owner, memory, on=True)
     owner = validation.testOwner(user, fields.getfirst('owner'))
 
     memory = fields.getfirst('memory')
     memory = validation.validMemory(owner, memory, on=True)
-    
+
     disk_size = fields.getfirst('disk')
     disk_size = validation.validDisk(owner, disk_size)
 
     vm_type = fields.getfirst('vmtype')
     disk_size = fields.getfirst('disk')
     disk_size = validation.validDisk(owner, disk_size)
 
     vm_type = fields.getfirst('vmtype')
-    if vm_type not in ('hvm', 'paravm'):
-        raise CodeError("Invalid vm type '%s'"  % vm_type)    
-    is_hvm = (vm_type == 'hvm')
+    vm_type = validation.validVmType(vm_type)
 
     cdrom = fields.getfirst('cdrom')
     if cdrom is not None and not CDROM.get(cdrom):
 
     cdrom = fields.getfirst('cdrom')
     if cdrom is not None and not CDROM.get(cdrom):
@@ -163,9 +170,9 @@ def parseCreate(user, fields):
     clone_from = fields.getfirst('clone_from')
     if clone_from and clone_from != 'ice3':
         raise CodeError("Invalid clone image '%s'" % clone_from)
     clone_from = fields.getfirst('clone_from')
     if clone_from and clone_from != 'ice3':
         raise CodeError("Invalid clone image '%s'" % clone_from)
-    
+
     return dict(contact=user, name=name, memory=memory, disk_size=disk_size,
     return dict(contact=user, name=name, memory=memory, disk_size=disk_size,
-                owner=owner, is_hvm=is_hvm, cdrom=cdrom, clone_from=clone_from)
+                owner=owner, machine_type=vm_type, cdrom=cdrom, clone_from=clone_from)
 
 def create(user, fields):
     """Handler for create requests."""
 
 def create(user, fields):
     """Handler for create requests."""
@@ -188,20 +195,27 @@ def create(user, fields):
 
 
 def getListDict(user):
 
 
 def getListDict(user):
+    """Gets the list of local variables used by list.tmpl."""
+    checkpoint.checkpoint('Starting')
     machines = g.machines
     checkpoint.checkpoint('Got my machines')
     on = {}
     has_vnc = {}
     machines = g.machines
     checkpoint.checkpoint('Got my machines')
     on = {}
     has_vnc = {}
-    on = g.uptimes
+    xmlist = g.xmlist
     checkpoint.checkpoint('Got uptimes')
     checkpoint.checkpoint('Got uptimes')
+    can_clone = (controls.getList([Machine.get_by(name='ice3')])) == {}
     for m in machines:
     for m in machines:
-        m.uptime = g.uptimes.get(m)
-        if not on[m]:
+        if m not in xmlist:
             has_vnc[m] = 'Off'
             has_vnc[m] = 'Off'
-        elif m.type.hvm:
-            has_vnc[m] = True
+            m.uptime = None
         else:
         else:
-            has_vnc[m] = "ParaVM"+helppopup("paravm_console")
+            m.uptime = xmlist[m]['uptime']
+            if xmlist[m]['console']:
+                has_vnc[m] = True
+            elif m.type.hvm:
+                has_vnc[m] = "WTF?"
+            else:
+                has_vnc[m] = "ParaVM"+helppopup("ParaVM Console")
     max_memory = validation.maxMemory(user)
     max_disk = validation.maxDisk(user)
     checkpoint.checkpoint('Got max mem/disk')
     max_memory = validation.maxMemory(user)
     max_disk = validation.maxDisk(user)
     checkpoint.checkpoint('Got max mem/disk')
@@ -220,8 +234,7 @@ def getListDict(user):
              defaults=defaults,
              machines=machines,
              has_vnc=has_vnc,
              defaults=defaults,
              machines=machines,
              has_vnc=has_vnc,
-             uptimes=g.uptimes,
-             cdroms=CDROM.select())
+             can_clone=can_clone)
     return d
 
 def listVms(user, fields):
     return d
 
 def listVms(user, fields):
@@ -230,7 +243,7 @@ def listVms(user, fields):
     d = getListDict(user)
     checkpoint.checkpoint('Got list dict')
     return templates.list(searchList=[d])
     d = getListDict(user)
     checkpoint.checkpoint('Got list dict')
     return templates.list(searchList=[d])
-            
+
 def vnc(user, fields):
     """VNC applet page.
 
 def vnc(user, fields):
     """VNC applet page.
 
@@ -242,9 +255,9 @@ def vnc(user, fields):
     You might want iptables like:
 
     -t nat -A PREROUTING -s ! 18.181.0.60 -i eth1 -p tcp -m tcp \
     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 
+      --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 \
     -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 
+      --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
 
     -A FORWARD -d 18.181.0.60 -i eth1 -o eth1 -p tcp -m tcp \
       --dport 10003 -j ACCEPT
 
@@ -252,7 +265,7 @@ def vnc(user, fields):
     echo 1 > /proc/sys/net/ipv4/ip_forward
     """
     machine = validation.testMachineId(user, fields.getfirst('machine_id'))
     echo 1 > /proc/sys/net/ipv4/ip_forward
     """
     machine = validation.testMachineId(user, fields.getfirst('machine_id'))
-    
+
     TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
 
     data = {}
     TOKEN_KEY = "0M6W0U1IXexThi5idy8mnkqPKEq1LtEnlK/pZSn0cDrN"
 
     data = {}
@@ -265,10 +278,10 @@ def vnc(user, fields):
     token = {'data': pickled_data, 'digest': m.digest()}
     token = cPickle.dumps(token)
     token = base64.urlsafe_b64encode(token)
     token = {'data': pickled_data, 'digest': m.digest()}
     token = cPickle.dumps(token)
     token = base64.urlsafe_b64encode(token)
-    
+
     status = controls.statusInfo(machine)
     has_vnc = hasVnc(status)
     status = controls.statusInfo(machine)
     has_vnc = hasVnc(status)
-    
+
     d = dict(user=user,
              on=status,
              has_vnc=has_vnc,
     d = dict(user=user,
              on=status,
              has_vnc=has_vnc,
@@ -278,10 +291,14 @@ def vnc(user, fields):
     return templates.vnc(searchList=[d])
 
 def getHostname(nic):
     return templates.vnc(searchList=[d])
 
 def getHostname(nic):
+    """Find the hostname associated with a NIC.
+
+    XXX this should be merged with the similar logic in DNS and DHCP.
+    """
     if nic.hostname and '.' in nic.hostname:
         return nic.hostname
     elif nic.machine:
     if nic.hostname and '.' in nic.hostname:
         return nic.hostname
     elif nic.machine:
-        return nic.machine.name + '.servers.csail.mit.edu'
+        return nic.machine.name + '.xvm.mit.edu'
     else:
         return None
 
     else:
         return None
 
@@ -319,7 +336,7 @@ def getDiskInfo(data_dict, machine):
     disk_fields = []
     for disk in machine.disks:
         name = disk.guest_device_name
     disk_fields = []
     for disk in machine.disks:
         name = disk.guest_device_name
-        disk_fields.extend([(x % name, y % name) for x, y in 
+        disk_fields.extend([(x % name, y % name) for x, y in
                             disk_fields_template])
         data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
     return disk_fields
                             disk_fields_template])
         data_dict['%s_size' % name] = "%0.1f GiB" % (disk.size / 1024.)
     return disk_fields
@@ -354,6 +371,10 @@ def command(user, fields):
         raise InvalidInput('back', back, 'Not a known back page.')
 
 def modifyDict(user, fields):
         raise InvalidInput('back', back, 'Not a known back page.')
 
 def modifyDict(user, fields):
+    """Modify a machine as specified by CGI arguments.
+
+    Return a list of local variables for modify.tmpl.
+    """
     olddisk = {}
     transaction = ctx.current.create_transaction()
     try:
     olddisk = {}
     transaction = ctx.current.create_transaction()
     try:
@@ -371,7 +392,11 @@ def modifyDict(user, fields):
         if memory is not None:
             memory = validation.validMemory(user, memory, machine, on=False)
             machine.memory = memory
         if memory is not None:
             memory = validation.validMemory(user, memory, machine, on=False)
             machine.memory = memory
+
+        vm_type = validation.validVmType(fields.getfirst('vmtype'))
+        if vm_type is not None:
+            machine.type = vm_type
+
         disksize = validation.testDisk(user, fields.getfirst('disk'))
         if disksize is not None:
             disksize = validation.validDisk(user, disksize, machine)
         disksize = validation.testDisk(user, fields.getfirst('disk'))
         if disksize is not None:
             disksize = validation.validDisk(user, disksize, machine)
@@ -380,17 +405,22 @@ def modifyDict(user, fields):
                 olddisk[disk.guest_device_name] = disksize
                 disk.size = disksize
                 ctx.current.save(disk)
                 olddisk[disk.guest_device_name] = disksize
                 disk.size = disksize
                 ctx.current.save(disk)
-        
-        if owner is not None:
+
+        update_acl = False
+        if owner is not None and owner != machine.owner:
             machine.owner = owner
             machine.owner = owner
+            update_acl = True
         if name is not None:
             machine.name = name
         if name is not None:
             machine.name = name
-        if admin is not None:
+        if admin is not None and admin != machine.administrator:
             machine.administrator = admin
             machine.administrator = admin
+            update_acl = True
         if contact is not None:
             machine.contact = contact
         if contact is not None:
             machine.contact = contact
-            
+
         ctx.current.save(machine)
         ctx.current.save(machine)
+        if update_acl:
+            cache_acls.refreshMachine(machine)
         transaction.commit()
     except:
         transaction.rollback()
         transaction.commit()
     except:
         transaction.rollback()
@@ -402,7 +432,7 @@ def modifyDict(user, fields):
     return dict(user=user,
                 command=command,
                 machine=machine)
     return dict(user=user,
                 command=command,
                 machine=machine)
-    
+
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
     try:
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
     try:
@@ -421,62 +451,65 @@ def modify(user, fields):
             setattr(info_dict['defaults'], field, fields.getfirst(field))
     info_dict['result'] = result
     return templates.info(searchList=[info_dict])
             setattr(info_dict['defaults'], field, fields.getfirst(field))
     info_dict['result'] = result
     return templates.info(searchList=[info_dict])
-    
+
 
 def helpHandler(user, fields):
     """Handler for help messages."""
     simple = fields.getfirst('simple')
     subjects = fields.getlist('subject')
 
 def helpHandler(user, fields):
     """Handler for help messages."""
     simple = fields.getfirst('simple')
     subjects = fields.getlist('subject')
-    
-    help_mapping = dict(paravm_console="""
-ParaVM machines do not support console access over VNC.  To access
-these machines, you either need to boot with a liveCD and ssh in or
-hope that the sipb-xen maintainers add support for serial consoles.""",
-                        hvm_paravm="""
+
+    help_mapping = {'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 console.xvm.mit.edu, using the name of the machine as your
+username.""",
+                    '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.""",
 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.""",
-                        cpu_weight="""
+                    'CPU Weight': """
 Don't ask us!  We're as mystified as you are.""",
 Don't ask us!  We're as mystified as you are.""",
-                        owner="""
+                    'Owner': """
 The owner field is used to determine <a
 The owner field is used to determine <a
-href="help?subject=quotas">quotas</a>.  It must be the name of 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 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 see who administers the LOCKER locker using the
-command 'fs la /mit/LOCKER' on Athena.)  See also <a
-href="help?subject=administrator">administrator</a>.""",
-                        administrator="""
+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.""",
 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': """
 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
 active machines.""",
 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
 active machines.""",
-                        console="""
+                    '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.
 """
 <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.
 """
-                   )
-    
+                    }
+
     if not subjects:
         subjects = sorted(help_mapping.keys())
     if not subjects:
         subjects = sorted(help_mapping.keys())
-        
+
     d = dict(user=user,
              simple=simple,
              subjects=subjects,
              mapping=help_mapping)
     d = dict(user=user,
              simple=simple,
              subjects=subjects,
              mapping=help_mapping)
-    
+
     return templates.help(searchList=[d])
     return templates.help(searchList=[d])
-    
+
 
 def badOperation(u, e):
 
 def badOperation(u, e):
+    """Function called when accessing an unknown URI."""
     raise CodeError("Unknown operation")
 
 def infoDict(user, machine):
     raise CodeError("Unknown operation")
 
 def infoDict(user, machine):
+    """Get the variables used by info.tmpl."""
     status = controls.statusInfo(machine)
     checkpoint.checkpoint('Getting status info')
     has_vnc = hasVnc(status)
     status = controls.statusInfo(machine)
     checkpoint.checkpoint('Getting status info')
     has_vnc = hasVnc(status)
@@ -505,7 +538,7 @@ def infoDict(user, machine):
                       ('memory', 'RAM'),
                       'DISK_INFO',
                       ('state', 'state (xen format)'),
                       ('memory', 'RAM'),
                       'DISK_INFO',
                       ('state', 'state (xen format)'),
-                      ('cpu_weight', 'CPU weight'+helppopup('cpu_weight')),
+                      ('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_reboot', 'Action on VM reboot'),
                       ('on_poweroff', 'Action on VM poweroff'),
                       ('on_crash', 'Action on VM crash'),
@@ -523,14 +556,14 @@ def infoDict(user, machine):
 
     nic_fields = getNicInfo(machine_info, machine)
     nic_point = display_fields.index('NIC_INFO')
 
     nic_fields = getNicInfo(machine_info, machine)
     nic_point = display_fields.index('NIC_INFO')
-    display_fields = (display_fields[:nic_point] + nic_fields + 
+    display_fields = (display_fields[:nic_point] + nic_fields +
                       display_fields[nic_point+1:])
 
     disk_fields = getDiskInfo(machine_info, machine)
     disk_point = display_fields.index('DISK_INFO')
                       display_fields[nic_point+1:])
 
     disk_fields = getDiskInfo(machine_info, machine)
     disk_point = display_fields.index('DISK_INFO')
-    display_fields = (display_fields[:disk_point] + disk_fields + 
+    display_fields = (display_fields[:disk_point] + disk_fields +
                       display_fields[disk_point+1:])
                       display_fields[disk_point+1:])
-    
+
     main_status['memory'] += ' MiB'
     for field, disp in display_fields:
         if field in ('uptime', 'cputime') and locals()[field] is not None:
     main_status['memory'] += ' MiB'
     for field, disp in display_fields:
         if field in ('uptime', 'cputime') and locals()[field] is not None:
@@ -552,10 +585,10 @@ def infoDict(user, machine):
     defaults = Defaults()
     for name in 'machine_id name administrator owner memory contact'.split():
         setattr(defaults, name, getattr(machine, name))
     defaults = Defaults()
     for name in 'machine_id name administrator owner memory contact'.split():
         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=user,
     defaults.disk = "%0.2f" % (machine.disks[0].size/1024.)
     checkpoint.checkpoint('Got defaults')
     d = dict(user=user,
-             cdroms=CDROM.select(),
              on=status is not None,
              machine=machine,
              defaults=defaults,
              on=status is not None,
              machine=machine,
              defaults=defaults,
@@ -564,7 +597,7 @@ def infoDict(user, machine):
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
-             owner_help=helppopup("owner"),
+             owner_help=helppopup("Owner"),
              fields = fields)
     return d
 
              fields = fields)
     return d
 
@@ -575,15 +608,21 @@ def info(user, fields):
     checkpoint.checkpoint('Got infodict')
     return templates.info(searchList=[d])
 
     checkpoint.checkpoint('Got infodict')
     return templates.info(searchList=[d])
 
+def unauthFront(_, fields):
+    """Information for unauth'd users."""
+    return templates.unauth(searchList=[{'simple' : True}])
+
 mapping = dict(list=listVms,
                vnc=vnc,
                command=command,
                modify=modify,
                info=info,
                create=create,
 mapping = dict(list=listVms,
                vnc=vnc,
                command=command,
                modify=modify,
                info=info,
                create=create,
-               help=helpHandler)
+               help=helpHandler,
+               unauth=unauthFront)
 
 def printHeaders(headers):
 
 def printHeaders(headers):
+    """Print a dictionary as HTTP headers."""
     for key, value in headers.iteritems():
         print '%s: %s' % (key, value)
     print
     for key, value in headers.iteritems():
         print '%s: %s' % (key, value)
     print
@@ -591,10 +630,12 @@ def printHeaders(headers):
 
 def getUser():
     """Return the current user based on the SSL environment variables"""
 
 def getUser():
     """Return the current user based on the SSL environment variables"""
-    username = os.environ['SSL_CLIENT_S_DN_Email'].split("@")[0]
-    return username
+    email = os.environ.get('SSL_CLIENT_S_DN_Email', None)
+    if email is None:
+        return None
+    return email.split("@")[0]
 
 
-def main(operation, user, fields):    
+def main(operation, user, fields):
     start_time = time.time()
     fun = mapping.get(operation, badOperation)
 
     start_time = time.time()
     fun = mapping.get(operation, badOperation)
 
@@ -616,7 +657,8 @@ def main(operation, user, fields):
         output_string =  str(output)
         checkpoint.checkpoint('output as a string')
         print output_string
         output_string =  str(output)
         checkpoint.checkpoint('output as a string')
         print output_string
-        print '<!-- <pre>%s</pre> -->' % checkpoint
+        if fields.has_key('timedebug'):
+            print '<pre>%s</pre>' % checkpoint
     except Exception, err:
         if not fields.has_key('js'):
             if isinstance(err, CodeError):
     except Exception, err:
         if not fields.has_key('js'):
             if isinstance(err, CodeError):
@@ -631,7 +673,7 @@ def main(operation, user, fields):
                 sys.exit(1)
         print 'Content-Type: text/plain\n'
         print 'Uh-oh!  We experienced an error.'
                 sys.exit(1)
         print 'Content-Type: text/plain\n'
         print 'Uh-oh!  We experienced an error.'
-        print 'Please email sipb-xen@mit.edu with the contents of this page.'
+        print 'Please email xvm-dev@mit.edu with the contents of this page.'
         print '----'
         e = revertStandardError()
         print e
         print '----'
         e = revertStandardError()
         print e
@@ -640,6 +682,13 @@ def main(operation, user, fields):
 
 if __name__ == '__main__':
     fields = cgi.FieldStorage()
 
 if __name__ == '__main__':
     fields = cgi.FieldStorage()
+
+    if fields.has_key('sqldebug'):
+        import logging
+        logging.basicConfig()
+        logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+        logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.INFO)
+
     u = getUser()
     g.user = u
     operation = os.environ.get('PATH_INFO', '')
     u = getUser()
     g.user = u
     operation = os.environ.get('PATH_INFO', '')
@@ -648,6 +697,9 @@ if __name__ == '__main__':
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
         sys.exit(0)
 
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
         sys.exit(0)
 
+    if u is None:
+        operation = 'unauth'
+
     if operation.startswith('/'):
         operation = operation[1:]
     if not operation:
     if operation.startswith('/'):
         operation = operation[1:]
     if not operation: