Create a static/ directory
[invirt/packages/invirt-web.git] / templates / main.py
index 8de8a37..ec1eca5 100755 (executable)
@@ -12,8 +12,10 @@ import base64
 import sha
 import hmac
 import datetime
 import sha
 import hmac
 import datetime
+import StringIO
+import getafsgroups
 
 
-sys.stderr = sys.stdout
+sys.stderr = StringIO.StringIO()
 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
 
 from Cheetah.Template import Template
 sys.path.append('/home/ecprice/.local/lib/python2.5/site-packages')
 
 from Cheetah.Template import Template
@@ -31,7 +33,10 @@ class InvalidInput(MyException):
     typo) but not setting an invalid boot CD (which requires bypassing
     the select box).
     """
     typo) but not setting an invalid boot CD (which requires bypassing
     the select box).
     """
-    pass
+    def __init__(self, err_field, err_value, expl=None):
+        MyException.__init__(self, expl)
+        self.err_field = err_field
+        self.err_value = err_value
 
 class CodeError(MyException):
     """Exception for internal errors or bad faith input."""
 
 class CodeError(MyException):
     """Exception for internal errors or bad faith input."""
@@ -43,7 +48,7 @@ class Global(object):
 
     def __get_uptimes(self):
         if not hasattr(self, '_uptimes'):
 
     def __get_uptimes(self):
         if not hasattr(self, '_uptimes'):
-            self._uptimes = getUptimes(self.machines)
+            self._uptimes = getUptimes(Machine.select())
         return self._uptimes
     uptimes = property(__get_uptimes)
 
         return self._uptimes
     uptimes = property(__get_uptimes)
 
@@ -82,16 +87,18 @@ def getMachinesByOwner(owner):
     """Return the machines owned by a given owner."""
     return Machine.select_by(owner=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.
 
     """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])
     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])
@@ -113,12 +120,20 @@ def haveAccess(user, machine):
     """Return whether a user has access to a machine"""
     if user.username == 'moo':
         return True
     """Return whether a user has access to a machine"""
     if user.username == 'moo':
         return True
-    return machine.owner == user.username
+    return getafsgroups.checkLockerOwner(user.username, machine.owner)
 
 
-def error(op, user, fields, err):
+def error(op, user, fields, err, emsg):
     """Print an error page when a CodeError occurs"""
     """Print an error page when a CodeError occurs"""
-    d = dict(op=op, user=user, errorMessage=str(err))
-    print Template(file='error.tmpl', searchList=[d, global_dict]);
+    d = dict(op=op, user=user, errorMessage=str(err),
+             stderr=emsg)
+    return Template(file='error.tmpl', searchList=[d, global_dict]);
+
+def invalidInput(op, user, fields, err, emsg):
+    """Print an error page when an InvalidInput exception occurs"""
+    d = dict(op=op, user=user, err_field=err.err_field,
+             err_value=str(err.err_value), stderr=emsg,
+             errorMessage=str(err))
+    return Template(file='invalid.tmpl', searchList=[d, global_dict]);
 
 def validMachineName(name):
     """Check that name is valid for a machine name"""
 
 def validMachineName(name):
     """Check that name is valid for a machine name"""
@@ -161,8 +176,9 @@ def remctl(*args, **kws):
         p.wait()
         return p.stdout.read(), p.stderr.read()
     if p.wait():
         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', args, ':'
+        print >> sys.stderr, p.stderr.read()
+        raise CodeError('ERROR on remctl')
     return p.stdout.read()
 
 def lvcreate(machine, disk):
     return p.stdout.read()
 
 def lvcreate(machine, disk):
@@ -219,7 +235,7 @@ def parseStatus(s):
             stack[-1].extend(v.split())
     return stack[-1]
 
             stack[-1].extend(v.split())
     return stack[-1]
 
-def getUptimes(machines):
+def getUptimes(machines=None):
     """Return a dictionary mapping machine names to uptime strings"""
     value_string = remctl('web', 'listvms')
     lines = value_string.splitlines()
     """Return a dictionary mapping machine names to uptime strings"""
     value_string = remctl('web', 'listvms')
     lines = value_string.splitlines()
@@ -265,11 +281,13 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
     transaction = ctx.current.create_transaction()
     try:
         if memory > maxMemory(user):
     transaction = ctx.current.create_transaction()
     try:
         if memory > maxMemory(user):
-            raise InvalidInput("Too much memory requested")
+            raise InvalidInput('memory', memory,
+                               "Max %s" % maxMemory(user))
         if disk > maxDisk(user) * 1024:
         if disk > maxDisk(user) * 1024:
-            raise InvalidInput("Too much disk requested")
+            raise InvalidInput('disk', disk,
+                               "Max %s" % maxDisk(user))
         if not canAddVm(user):
         if not canAddVm(user):
-            raise InvalidInput("Too many VMs requested")
+            raise InvalidInput('create', True, 'Unable to create more VMs')
         res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
         id = res.fetchone()[0]
         machine = Machine()
         res = meta.engine.execute('select nextval(\'"machines_machine_id_seq"\')')
         id = res.fetchone()[0]
         machine = Machine()
@@ -277,6 +295,7 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
         machine.name = name
         machine.memory = memory
         machine.owner = user.username
         machine.name = name
         machine.memory = memory
         machine.owner = user.username
+        machine.administrator = user.username
         machine.contact = user.email
         machine.uuid = uuidToString(randomUUID())
         machine.boot_off_cd = True
         machine.contact = user.email
         machine.uuid = uuidToString(randomUUID())
         machine.boot_off_cd = True
@@ -304,17 +323,22 @@ def createVm(user, name, memory, disk, is_hvm, cdrom):
 
     return machine
 
 
     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:
             raise ValueError
     except ValueError:
     try:
         memory = int(memory)
         if memory < MIN_MEMORY_SINGLE:
             raise ValueError
     except ValueError:
-        raise InvalidInput("Invalid memory amount; must be at least %s MB" %
-                          MIN_MEMORY_SINGLE)
-    if memory > maxMemory(user, machine):
-        raise InvalidInput("Too much memory requested")
+        raise InvalidInput('memory', memory, 
+                           "Minimum %s MB" % MIN_MEMORY_SINGLE)
+    if memory > maxMemory(user, machine, on):
+        raise InvalidInput('memory', memory,
+                           'Maximum %s MB' % maxMemory(user, machine))
     return memory
 
 def validDisk(user, disk, machine=None):
     return memory
 
 def validDisk(user, disk, machine=None):
@@ -322,27 +346,29 @@ def validDisk(user, disk, machine=None):
     try:
         disk = float(disk)
         if disk > maxDisk(user, machine):
     try:
         disk = float(disk)
         if disk > maxDisk(user, machine):
-            raise InvalidInput("Too much disk requested")
+            raise InvalidInput('disk', disk,
+                               "Maximum %s G" % maxDisk(user, machine))
         disk = int(disk * 1024)
         if disk < MIN_DISK_SINGLE * 1024:
             raise ValueError
     except ValueError:
         disk = int(disk * 1024)
         if disk < MIN_DISK_SINGLE * 1024:
             raise ValueError
     except ValueError:
-        raise InvalidInput("Invalid disk amount; minimum is %s GB" %
-                          MIN_DISK_SINGLE)
+        raise InvalidInput('disk', disk,
+                           "Minimum %s GB" % MIN_DISK_SINGLE)
     return disk
 
 def create(user, fields):
     """Handler for create requests."""
     name = fields.getfirst('name')
     if not validMachineName(name):
     return disk
 
 def create(user, fields):
     """Handler for create requests."""
     name = fields.getfirst('name')
     if not validMachineName(name):
-        raise InvalidInput("Invalid name '%s'" % name)
-    name = user.username + '_' + name.lower()
+        raise InvalidInput('name', name)
+    name = name.lower()
 
     if Machine.get_by(name=name):
 
     if Machine.get_by(name=name):
-        raise InvalidInput("A machine named '%s' already exists" % name)
+        raise InvalidInput('name', name,
+                           "Already exists")
     
     memory = fields.getfirst('memory')
     
     memory = fields.getfirst('memory')
-    memory = validMemory(user, memory)
+    memory = validMemory(user, memory, on=True)
     
     disk = fields.getfirst('disk')
     disk = validDisk(user, disk)
     
     disk = fields.getfirst('disk')
     disk = validDisk(user, disk)
@@ -359,7 +385,7 @@ def create(user, fields):
     machine = createVm(user, name, memory, disk, is_hvm, cdrom)
     d = dict(user=user,
              machine=machine)
     machine = createVm(user, name, memory, disk, is_hvm, cdrom)
     d = dict(user=user,
              machine=machine)
-    print Template(file='create.tmpl',
+    return Template(file='create.tmpl',
                    searchList=[d, global_dict]);
 
 def listVms(user, fields):
                    searchList=[d, global_dict]);
 
 def listVms(user, fields):
@@ -389,9 +415,9 @@ def listVms(user, fields):
              default_disk=min(4.0, max_disk),
              machines=machines,
              has_vnc=has_vnc,
              default_disk=min(4.0, max_disk),
              machines=machines,
              has_vnc=has_vnc,
-             uptimes=uptimes,
+             uptimes=g.uptimes,
              cdroms=CDROM.select())
              cdroms=CDROM.select())
-    print Template(file='list.tmpl', searchList=[d, global_dict])
+    return Template(file='list.tmpl', searchList=[d, global_dict])
 
 def testMachineId(user, machineId, exists=True):
     """Parse, validate and check authorization for a given machineId.
 
 def testMachineId(user, machineId, exists=True):
     """Parse, validate and check authorization for a given machineId.
@@ -452,7 +478,7 @@ def vnc(user, fields):
              machine=machine,
              hostname=os.environ.get('SERVER_NAME', 'localhost'),
              authtoken=token)
              machine=machine,
              hostname=os.environ.get('SERVER_NAME', 'localhost'),
              authtoken=token)
-    print Template(file='vnc.tmpl',
+    return Template(file='vnc.tmpl',
                    searchList=[d, global_dict])
 
 def getNicInfo(data_dict, machine):
                    searchList=[d, global_dict])
 
 def getNicInfo(data_dict, machine):
@@ -493,6 +519,7 @@ def getDiskInfo(data_dict, machine):
 
 def deleteVM(machine):
     """Delete a VM."""
 
 def deleteVM(machine):
     """Delete a VM."""
+    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:
     transaction = ctx.current.create_transaction()
     delete_disk_pairs = [(machine.name, d.guest_device_name) for d in machine.disks]
     try:
@@ -513,11 +540,11 @@ def deleteVM(machine):
 
 def command(user, fields):
     """Handler for running commands like boot and delete on a VM."""
 
 def command(user, fields):
     """Handler for running commands like boot and delete on a VM."""
-    print time.time()-start_time
+    print >> sys.stderr, time.time()-start_time
     machine = testMachineId(user, fields.getfirst('machine_id'))
     action = fields.getfirst('action')
     cdrom = fields.getfirst('cdrom')
     machine = testMachineId(user, fields.getfirst('machine_id'))
     action = fields.getfirst('action')
     cdrom = fields.getfirst('cdrom')
-    print time.time()-start_time
+    print >> sys.stderr, time.time()-start_time
     if cdrom is not None and not CDROM.get(cdrom):
         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
     if cdrom is not None and not CDROM.get(cdrom):
         raise CodeError("Invalid cdrom type '%s'" % cdrom)    
     if action not in ('Reboot', 'Power on', 'Power off', 'Shutdown', 'Delete VM'):
@@ -529,7 +556,8 @@ def command(user, fields):
             remctl('reboot', machine.name)
     elif action == 'Power on':
         if maxMemory(user) < machine.memory:
             remctl('reboot', machine.name)
     elif action == 'Power on':
         if maxMemory(user) < machine.memory:
-            raise InvalidInput("You don't have enough free RAM quota")
+            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':
         remctl('destroy', machine.name)
         bootMachine(machine, cdrom)
     elif action == 'Power off':
         remctl('destroy', machine.name)
@@ -537,18 +565,107 @@ def command(user, fields):
         remctl('shutdown', machine.name)
     elif action == 'Delete VM':
         deleteVM(machine)
         remctl('shutdown', machine.name)
     elif action == 'Delete VM':
         deleteVM(machine)
-    print time.time()-start_time
+    print >> sys.stderr, time.time()-start_time
 
     d = dict(user=user,
              command=action,
              machine=machine)
 
     d = dict(user=user,
              command=action,
              machine=machine)
-    print Template(file="command.tmpl", searchList=[d, global_dict])
-        
+    return Template(file="command.tmpl", searchList=[d, global_dict])
+
+def testOwner(user, owner, machine=None):
+    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 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):
+    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):
+        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."""
 def modify(user, fields):
     """Handler for modifying attributes of a machine."""
-    #XXX not written yet
-    machine = testMachineId(user, fields.getfirst('machine_id'))
-    
+
+    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)
+        oldname = machine.name
+        command="modify"
+
+        memory = fields.getfirst('memory')
+        if memory is not None:
+            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)
+        
+        # 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 is not None and owner != machine.owner:
+            machine.owner = owner
+        if name is not None and name != machine.name:
+            machine.name = name
+            
+        ctx.current.save(machine)
+        transaction.commit()
+    except:
+        transaction.rollback()
+        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=command,
+             machine=machine)
+    return Template(file="command.tmpl", searchList=[d, global_dict])    
+
+
 def help(user, fields):
     """Handler for help messages."""
     simple = fields.getfirst('simple')
 def help(user, fields):
     """Handler for help messages."""
     simple = fields.getfirst('simple')
@@ -562,14 +679,19 @@ 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.""",
 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,
              subjects=subjects,
              mapping=mapping)
     
     
     d = dict(user=user,
              simple=simple,
              subjects=subjects,
              mapping=mapping)
     
-    print Template(file="help.tmpl", searchList=[d, global_dict])
+    return Template(file="help.tmpl", searchList=[d, global_dict])
     
 
 def info(user, fields):
     
 
 def info(user, fields):
@@ -580,12 +702,14 @@ def info(user, fields):
     if status is None:
         main_status = dict(name=machine.name,
                            memory=str(machine.memory))
     if status is None:
         main_status = dict(name=machine.name,
                            memory=str(machine.memory))
+        uptime=None
+        cputime=None
     else:
         main_status = dict(status[1:])
     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'),
     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'),
@@ -623,7 +747,7 @@ def info(user, fields):
     
     main_status['memory'] += ' MB'
     for field, disp in display_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]))
             fields.append((disp, locals()[field]))
         elif field in machine_info:
             fields.append((disp, machine_info[field]))
@@ -643,8 +767,9 @@ def info(user, fields):
              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"),
              fields = fields)
              fields = fields)
-    print Template(file='info.tmpl',
+    return Template(file='info.tmpl',
                    searchList=[d, global_dict])
 
 mapping = dict(list=listVms,
                    searchList=[d, global_dict])
 
 mapping = dict(list=listVms,
@@ -672,28 +797,49 @@ if __name__ == '__main__':
         u.email = 'nobody'
     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     operation = os.environ.get('PATH_INFO', '')
         u.email = 'nobody'
     connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     operation = os.environ.get('PATH_INFO', '')
-    #print 'Content-Type: text/plain\n'
-    #print operation
     if not operation:
         print "Status: 301 Moved Permanently"
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
         sys.exit(0)
     if not operation:
         print "Status: 301 Moved Permanently"
         print 'Location: ' + os.environ['SCRIPT_NAME']+'/\n'
         sys.exit(0)
-    print 'Content-Type: text/html\n'
 
     if operation.startswith('/'):
         operation = operation[1:]
     if not operation:
         operation = 'list'
 
     if operation.startswith('/'):
         operation = operation[1:]
     if not operation:
         operation = 'list'
-    
-    fun = mapping.get(operation, 
-                      lambda u, e:
-                          error(operation, u, e,
-                                "Invalid operation '%s'" % operation))
+
+    def badOperation(u, e):
+        raise CodeError("Unknown operation")
+
+    fun = mapping.get(operation, badOperation)
     if fun not in (help, ):
         connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     try:
     if fun not in (help, ):
         connect('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
     try:
-        fun(u, fields)
+        output = fun(u, fields)
+        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>')
+        print output
     except CodeError, err:
     except CodeError, err:
-        error(operation, u, fields, err)
+        print 'Content-Type: text/html\n'
+        sys.stderr.seek(0)
+        e = sys.stderr.read()
+        sys.stderr=sys.stdout
+        print error(operation, u, fields, err, e)
     except InvalidInput, err:
     except InvalidInput, err:
-        error(operation, u, fields, err)
+        print 'Content-Type: text/html\n'
+        sys.stderr.seek(0)
+        e = sys.stderr.read()
+        sys.stderr=sys.stdout
+        print invalidInput(operation, u, fields, err, e)
+    except:
+        print 'Content-Type: text/plain\n'
+        sys.stderr.seek(0)
+        e = sys.stderr.read()
+        print e
+        print '----'
+        sys.stderr = sys.stdout
+        raise