Several modifications.
[invirt/packages/invirt-web.git] / templates / main.py
index 36ea239..74a6116 100755 (executable)
@@ -34,7 +34,7 @@ class InvalidInput(MyException):
     the select box).
     """
     def __init__(self, err_field, err_value, expl=None):
-        super(InvalidInput, self).__init__(expl)
+        MyException.__init__(self, expl)
         self.err_field = err_field
         self.err_value = err_value
 
@@ -87,16 +87,18 @@ def getMachinesByOwner(owner):
     """Return the machines owned by a given owner."""
     return Machine.select_by(owner=owner)
 
-def maxMemory(user, machine=None):
+def maxMemory(user, machine=None, on=True):
     """Return the maximum memory for a machine or a user.
 
     If machine is None, return the memory available for a new 
     machine.  Else, return the maximum that machine can have.
 
-    on is a dictionary from machines to booleans, whether a machine is
-    on.  If None, it is recomputed. XXX make this global?
+    on is whether the machine should be turned on.  If false, the max
+    memory for the machine to change to, if it is left off, is
+    returned.
     """
-
+    if not on:
+        return MAX_MEMORY_SINGLE
     machines = getMachinesByOwner(user.username)
     active_machines = [x for x in machines if g.uptimes[x]]
     mem_usage = sum([x.memory for x in active_machines if x != machine])
@@ -118,7 +120,7 @@ def haveAccess(user, machine):
     """Return whether a user has access to a machine"""
     if user.username == 'moo':
         return True
-    return getafsgroups.checkLockerOwner(user.username,machine.owner)
+    return getafsgroups.checkLockerOwner(user.username, machine.owner)
 
 def error(op, user, fields, err, emsg):
     """Print an error page when a CodeError occurs"""
@@ -174,8 +176,9 @@ def remctl(*args, **kws):
         p.wait()
         return p.stdout.read(), p.stderr.read()
     if p.wait():
-        raise CodeError('ERROR on remctl %s: %s' %
-                          (args, p.stderr.read()))
+        print >> sys.stderr, 'Error on remctl %s:' % args
+        print >> sys.stderr, p.stderr.read()
+        raise CodeError('ERROR on remctl')
     return p.stdout.read()
 
 def lvcreate(machine, disk):
@@ -319,8 +322,12 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
 
     return machine
 
-def validMemory(user, memory, machine=None):
-    """Parse and validate limits for memory for a given user and machine."""
+def validMemory(user, memory, machine=None, on=True):
+    """Parse and validate limits for memory for a given user and machine.
+
+    on is whether the memory must be valid after the machine is
+    switched on.
+    """
     try:
         memory = int(memory)
         if memory < MIN_MEMORY_SINGLE:
@@ -328,7 +335,7 @@ def validMemory(user, memory, machine=None):
     except ValueError:
         raise InvalidInput('memory', memory, 
                            "Minimum %s MB" % MIN_MEMORY_SINGLE)
-    if memory > maxMemory(user, machine):
+    if memory > maxMemory(user, machine, on):
         raise InvalidInput('memory', memory,
                            'Maximum %s MB' % maxMemory(user, machine))
     return memory
@@ -360,7 +367,7 @@ def create(user, fields):
                            "Already exists")
     
     memory = fields.getfirst('memory')
-    memory = validMemory(user, memory)
+    memory = validMemory(user, memory, on=True)
     
     disk = fields.getfirst('disk')
     disk = validDisk(user, disk)
@@ -511,10 +518,7 @@ def getDiskInfo(data_dict, machine):
 
 def deleteVM(machine):
     """Delete a VM."""
-    try:
-        remctl('destroy', machine.name)
-    except:
-        pass
+    remctl('destroy', machine.name, err=True)
     transaction = ctx.current.create_transaction()
     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
     try:
@@ -568,87 +572,95 @@ def command(user, fields):
     return Template(file="command.tmpl", searchList=[d, global_dict])
 
 def testOwner(user, owner, machine=None):
-    if not getafsgroups.checkLockerOwner(user.username, owner):
-        raise InvalidInput('owner', owner,
-                           "Invalid")
-    return owner
+    if owner == machine.owner:   #XXX What do we do when you lose access to the locker?
+        return owner
+    value = getafsgroups.checkLockerOwner(user.username, owner, verbose=True)
+    if value == True:
+        return owner
+    raise InvalidInput('owner', owner, value)
 
 def testContact(user, contact, machine=None):
-    if contact != user.email:
-        raise InvalidInput('contact', contact,
-                           "Invalid")
+    if not re.match("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", contact, re.I):
+        raise InvalidInput('contact', contact, "Not a valid email")
     return contact
 
 def testDisk(user, disksize, machine=None):
     return disksize
 
 def testName(user, name, machine=None):
-    return name
+    if name is None:
+        return None
+    if not Machine.select_by(name=name):
+        return name
+    if name == machine.name:
+        return name
+    raise InvalidInput('name', name, "Already taken")
 
 def testHostname(user, hostname, machine):
     for nic in machine.nics:
         if hostname == nic.hostname:
             return hostname
     # check if doesn't already exist
-    if NIC.select_by(hostname=hostname) == []:
-        return hostname
-    raise InvalidInput('hostname', hostname,
-                       "Different from before")
-
+    if NIC.select_by(hostname=hostname):
+        raise InvalidInput('hostname', hostname,
+                           "Already exists")
+    if not re.match("^[A-Z0-9-]{1,22}$", hostname, re.I):
+        raise InvalidInput('hostname', hostname, "Not a valid hostname; must only use number, letters, and dashes.")
+    return hostname
 
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
-    #XXX not written yet
 
+    olddisk = {}
     transaction = ctx.current.create_transaction()
     try:
         machine = testMachineId(user, fields.getfirst('machine_id'))
         owner = testOwner(user, fields.getfirst('owner'), machine)
         contact = testContact(user, fields.getfirst('contact'))
         hostname = testHostname(owner, fields.getfirst('hostname'),
-                            machine)
-        name = testName(user, fields.getfirst('name'))
+                                machine)
+        name = testName(user, fields.getfirst('name'), machine)
         oldname = machine.name
-        olddisk = {}
+        command="modify"
 
         memory = fields.getfirst('memory')
         if memory is not None:
-            memory = validMemory(user, memory, machine)
-        if memory != machine.memory:
+            memory = validMemory(user, memory, machine, on=False)
             machine.memory = memory
-
         disksize = testDisk(user, fields.getfirst('disk'))
         if disksize is not None:
             disksize = validDisk(user, disksize, machine)
+            disk = machine.disks[0]
+            if disk.size != disksize:
+                olddisk[disk.guest_device_name] = disksize
+                disk.size = disksize
+                ctx.current.save(disk)
         
-        for disk in machine.disks:
-            disk.size = disksize
-            olddisk[disk.guest_device_name] = disk.size
-            ctx.current.save(disk)
-        
-        # XXX all NICs get same hostname on change?  Interface doesn't support more.
-        for nic in machine.nics:
+        # XXX first NIC gets hostname on change?  Interface doesn't support more.
+        for nic in machine.nics[:1]:
             nic.hostname = hostname
             ctx.current.save(nic)
 
-        if owner != machine.owner:
+        if owner is not None and owner != machine.owner:
             machine.owner = owner
-        if name != machine.name:
+        if name is not None and name != machine.name:
             machine.name = name
             
         ctx.current.save(machine)
         transaction.commit()
     except:
         transaction.rollback()
-    remctl("web", "moveregister", oldname, name)
-    for disk in machine.disks:
-        # XXX all disks get the same size on change?  Interface doesn't support more.
-        if disk.size != olddisk[disk.guest_device_name]:
-            remctl("web", "lvresize", oldname, disk.guest_device_name, str(disk.size))
-        if oldname != name:
-            remctl("web", "lvrename", oldname, disk.guest_device_name, name)
+        raise
+    for diskname in olddisk:
+        remctl("web", "lvresize", oldname, diskname, str(olddisk[diskname]))
+    if name is not None and name != oldname:
+        for disk in machine.disks:
+            if oldname != name:
+                remctl("web", "lvrename", oldname, disk.guest_device_name, name)
+        remctl("web", "moveregister", oldname, name)
     d = dict(user=user,
-             command="modify",
+             command=command,
              machine=machine)
     return Template(file="command.tmpl", searchList=[d, global_dict])    
 
@@ -666,7 +678,12 @@ hope that the sipb-xen maintainers add support for serial consoles.""",
 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="""Don't ask us!  We're as mystified as you are.""")
+                   cpu_weight="""Don't ask us!  We're as mystified as you are.""",
+                   owner="""The Owner 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 see who
+administers the LOCKER locker using the command 'fs la /mit/LOCKER' on
+Athena.)""")
     
     d = dict(user=user,
              simple=simple,
@@ -684,12 +701,14 @@ def info(user, fields):
     if status is None:
         main_status = dict(name=machine.name,
                            memory=str(machine.memory))
+        uptime=None
+        cputime=None
     else:
         main_status = dict(status[1:])
-    start_time = float(main_status.get('start_time', 0))
-    uptime = datetime.timedelta(seconds=int(time.time()-start_time))
-    cpu_time_float = float(main_status.get('cpu_time', 0))
-    cputime = datetime.timedelta(seconds=int(cpu_time_float))
+        start_time = float(main_status.get('start_time', 0))
+        uptime = datetime.timedelta(seconds=int(time.time()-start_time))
+        cpu_time_float = float(main_status.get('cpu_time', 0))
+        cputime = datetime.timedelta(seconds=int(cpu_time_float))
     display_fields = """name uptime memory state cpu_weight on_reboot 
      on_poweroff on_crash on_xend_start on_xend_stop bootloader""".split()
     display_fields = [('name', 'Name'),
@@ -727,7 +746,7 @@ def info(user, fields):
     
     main_status['memory'] += ' MB'
     for field, disp in display_fields:
-        if field in ('uptime', 'cputime'):
+        if field in ('uptime', 'cputime') and locals()[field] is not None:
             fields.append((disp, locals()[field]))
         elif field in machine_info:
             fields.append((disp, machine_info[field]))
@@ -747,6 +766,7 @@ def info(user, fields):
              ram=machine.memory,
              max_mem=max_mem,
              max_disk=max_disk,
+             owner_help=helppopup("owner"),
              fields = fields)
     return Template(file='info.tmpl',
                    searchList=[d, global_dict])
@@ -799,6 +819,7 @@ if __name__ == '__main__':
         print 'Content-Type: text/html\n'
         sys.stderr.seek(0)
         e = sys.stderr.read()
+        sys.stderr=sys.stdout
         if e:
             output = str(output)
             output = output.replace('<body>', '<body><p>STDERR:</p><pre>'+e+'</pre>')