text:
from __future__ import generators
import os
import time
import thread
import sys
import xmlrpclib
import traceback
from SimpleXMLRPCServer import SimpleXMLRPCServer
import socket

import dbfile
import logging

def get_hostname(host=None):
    'get FQDN for host, or current host if not specified'
    if host is None:
        host=socket.gethostname()
    try:
        return socket.gethostbyaddr(host)[0]
    except socket.herror: # DNS CAN'T RESOLVE HOSTNAME
        return host # JUST USE HOSTNAME AS REPORTED BY gethostname()

def get_server(host, port, logRequests=False):
    """Start xmlrpc server on requested host:port.

    Return bound SimpleXMLRPCServer server obj and port it's bound to.

    Set port=0 to bind to a random port number.
    """
    if host is None: # use localhost as default
        host='localhost'
    server = SimpleXMLRPCServer((host, port), logRequests=logRequests)
    port = server.socket.getsockname()[1]
    logging.info("Running XMLRPC server on port %d..." % port)
    return server, port

class XMLRPCClientObject(object):
    'provides object proxy for remote object, with methods that mirror its xmlrpc_methods'
    def __init__(self,server,name,methodDict):
        self.name=name
        self.server=server
        import new
        class methodcall(object):
            def __init__(self,name):
                self.name=name
            def __call__(self,obj,*args):
                return obj.server.server.methodCall(obj.name,self.name,args)
        for methodName in methodDict: # CREATE METHODS TO ACCESS REMOTE OBJECT'S METHODS
            setattr(self,methodName,new.instancemethod(methodcall(methodName),self,self.__class__))

class XMLRPCClient(dict):
    'interface to XMLRPC server serving multiple named objects'
    def __init__(self,url):
        self.server=xmlrpclib.ServerProxy(url)
    def __getitem__(self,name):
        'get connection to the named server object'
        try:
            return dict.__getitem__(self,name)
        except KeyError:
            methodDict=self.server.objectInfo(name) # GET INFO ABOUT REQUESTED OBJECT
            import types
            if isinstance(methodDict,types.StringType):
                raise KeyError(methodDict) # RETURNED VALUE IS ERROR MESSAGE!
            v=XMLRPCClientObject(self,name,methodDict)
            self[name]=v # SAVE THIS OBJECT INTO OUR DICTIONARY
            return v

class ConnectionDict(dict):
    'ensure that multiple requests for the same connection use same ServerProxy'
    def __call__(self,url,name):
        try:
            s=self[url] # REUSE EXISTING CONNECTION TO THE SERVER
        except KeyError:
            s=XMLRPCClient(url) # GET NEW CONNECTION TO THE SERVER
            self[url]=s # CACHE THIS CONNECTION
        return s[name] # GET THE REQUESTED OBJECT PROXY FROM THE SERVER

get_connection=ConnectionDict() # THIS RETURNS SAME ServerProxy FOR SAME url

def safe_dispatch(self,name,args):
    """restrict calls to selected methods, and trap all exceptions to
    keep server alive!"""
    import datetime
    if name in self.xmlrpc_methods: # MAKE SURE THIS METHOD IS EXPLICITLY ALLOWED
        try: # TRAP ALL ERRORS TO PREVENT OUR SERVER FROM DYING
            print >>sys.stderr,'XMLRPC:',name,args,\
                  datetime.datetime.now().isoformat(' ') # LOG THE REQUEST
            if self.xmlrpc_methods[name]: # use this as an alias for method
                m = getattr(self,self.xmlrpc_methods[name])
            else: # use method name as usual
                m = getattr(self,name) # GET THE BOUND METHOD
            val=m(*args) # CALL THE METHOD
            sys.stderr.flush() # FLUSH ANY OUTPUT TO OUR LOG
            return val # HAND BACK ITS RETURN VALUE
        except SystemExit:
            raise  # WE REALLY DO WANT TO EXIT.
        except: # METHOD RAISED AN EXCEPTION, SO PRINT TRACEBACK TO STDERR
            traceback.print_exc(self.max_tb,sys.stderr)
    else:
        print >>sys.stderr,"safe_dispatch: blocked unregistered method %s" % name
    return False # THIS RETURN VALUE IS CONFORMABLE BY XMLRPC...

class ObjectFromString(list):
    """convenience class for initialization from string of format:
    val1,val2,foo=12,bar=39,sshopts=-1 -p 1234
    Args of format name=val are saved on the object as attributes;
    otherwise each arg is saved as a list.
    Argument type conversion is performed automatically if attrtype
    mapping provided either to constructor or by the class itself.
    Numeric keys in this mapping are applied to the corresponding
    list arguments; string keys in this mapping are applied to
    the corresponding attribute arguments.
    Both the argument separator and assignment separator can be
    customized."""
    _separator=','
    _eq_separator='='
    def __init__(self,s,separator=None,eq_separator=None):
        list.__init__(self)
        if separator is None:
            separator=self._separator
        if eq_separator is None:
            eq_separator=self._eq_separator
        args=s.split(separator)
        i=0
        for arg in args:
            try: # PROCESS attr=val ARGUMENT FORMAT
                k,v=arg.split(eq_separator)
                try: # SEE IF WE HAVE A TYPE FOR THIS ATTRIBUTE
                    v=self._attrtype[k](v)
                except (AttributeError,KeyError):
                    pass # IF NO CONVERSION, JUST USE THE ORIGINAL STRING
                setattr(self,k,v) # SAVE VALUE AS ATTRIBUTE
            except ValueError: # JUST A SIMPLE ARGUMENT, SO SAVE AS ARG LIST
                try: # SEE IF WE HAVE A TYPE FOR THIS LIST ITEM
                    arg=self._attrtype[i](arg)
                except (AttributeError,KeyError):
                    pass # IF NO CONVERSION, JUST USE THE ORIGINAL STRING
                self.append(arg)
                i+=1 # ADVANCE OUR ARGUMENT COUNT

class FileDict(dict):
    "read key,value pairs as WS-separated lines, with objclass(value) conversion"
    def __init__(self,filename,objclass=str):
        dict.__init__(self)
        f=file(filename, 'rU') # text file
        for line in f:
            key=line.split()[0] # GET THE 1ST ARGUMENT
            val=line[len(key):].lstrip().rstrip() # GET THE REST, STRIP OUTER WS
            self[key]=objclass(val) # APPLY THE DESIRED TYPE CONVERSION
        f.close()

def try_fork():
    "standard UNIX technique c/o Jurgen Hermann's Python Cookbook recipe"
    try:
        pid=os.fork()
        if pid>0: # MAKE PARENT EXIT SILENTLY
            sys.exit(0)
    except OSError,e:
        print >>sys.stderr, "fork failed: %d (%s)" %(e.errno,e.strerror)
        sys.exit(1)

def detach_as_demon_process(self):
    "standard UNIX technique c/o Jurgen Hermann's Python Cookbook recipe"
    # CREATE AN APPROPRIATE ERRORLOG FILEPATH
    if not hasattr(self,'errlog') or self.errlog is False:
        self.errlog = os.path.join(os.getcwd(), self.name + '.log')
    try_fork() # DISCONNECT FROM PARENT PROCESS
    #os.chdir("/")
    os.setsid() # CREATE A NEW SESSION WITH NO CONTROLLING TERMINAL
    os.umask(0) # IS THIS ABSOLUTELY NECESSARY?
    try_fork() # DISCONNECT FROM PARENT PROCESS
    sys.stdout=file(self.errlog,'a') # DEMONIZE BY REDIRECTING ALL OUTPUT TO LOG
    sys.stderr=sys.stdout

def serve_forever(self):
    'start the service -- this will run forever'
    import datetime
    print >>sys.stderr,"START_SERVER:%s %s" %(self.name,datetime.datetime.
                                                   now().isoformat(' '))
    sys.stderr.flush()
    self.server.serve_forever()

class CoordinatorInfo(object):
    """stores information about individual coordinators for the controller
    and provides interface to Coordinator that protects against possibility of
    deadlock."""
    min_startup_time=60.0
    def __init__(self,name,url,user,priority,resources,job_id=0,immediate=False,
                 demand_ncpu=0):
        self.name=name
        self.url=url
        self.user=user
        self.priority=priority
        self.job_id=job_id
        self.immediate=immediate
        self.server=xmlrpclib.ServerProxy(url)
        self.processors={}
        self.resources=resources
        self.start_time=time.time()
        self.demand_ncpu=demand_ncpu # SET TO NON-ZERO IF YOU WANT FIXED #CPUS
        self.allocated_ncpu=0
        self.new_cpus=[]
        self.last_start_proc_time=0.0

    def __iadd__(self,newproc):
        "add a processor to this coordinator's list"
        self.processors[newproc]=time.time()
        return self

    def __isub__(self,oldproc):
        "remove a processor from this coordinator's list"
        del self.processors[oldproc]
        return self

    def update_load(self):
        """tell this coordinator to use only allocated_ncpu processors,
        and to launch processors on the list of new_cpus.
        Simply spawns a thread to do this without danger of deadlock"""
        import threading
        t=threading.Thread(target=self.update_load_thread,
                           args=(self.allocated_ncpu,self.new_cpus))
        self.new_cpus=[] # DISCONNECT FROM OLD LIST TO PREVENT OVERWRITING
        t.start()

    def update_load_thread(self,ncpu,new_cpus):
        """tell this coordinator to use only ncpu processors,
        and to launch processors on the list of new_cpus.
        Run this in a separate thread to prevent deadlock."""
        self.server.set_max_clients(ncpu)
        if len(new_cpus)>0 and \
               time.time()-self.last_start_proc_time>self.min_startup_time:
            self.server.start_processors(new_cpus) # SEND OUR LIST
            self.last_start_proc_time=time.time()

class HostInfo(ObjectFromString):
    _attrtype={'maxload':float}

class XMLRPCServerBase(object):
    'Base class for creating an XMLRPC server for multiple objects'
    xmlrpc_methods={'methodCall':0,'objectList':0,'objectInfo':0}
    max_tb=10
    _dispatch=safe_dispatch # RESTRICT XMLRPC TO JUST THE METHODS LISTED ABOVE
    def __init__(self, name, host=None, port=5000, logRequests=False,
                 server=None):
        if host is None: # GET FULLY QUALIFIED HOSTNAME SO OTHERS CAN CONTACT US
            host=get_hostname()
        self.host=host
        self.name=name
        if server is not None:
            self.server = server
            self.port = port
        else:
            self.server,self.port = get_server(host, port, logRequests)
        self.server.register_instance(self)
        self.objDict={}
    def __setitem__(self,name,obj):
        'add a new object to serve'
        self.objDict[name]=obj
    def __delitem__(self,name):
        del self.objDict[name]
    def objectList(self):
        'get list of named objects in this server: [(name,methodDict),...]'
        return [(name,obj.xmlrpc_methods) for (name,obj) in self.objDict.items()]
    def objectInfo(self,objname):
        'get dict of methodnames on the named object'
        try:
            return self.objDict[objname].xmlrpc_methods
        except KeyError:
            return 'error: server has no object named %s' % objname
    def methodCall(self,objname,methodname,args):
        'run the named method on the named object and return its result'
        try:
            obj=self.objDict[objname]
            if methodname in obj.xmlrpc_methods:
                m=getattr(obj,methodname)
            else:
                print >>sys.stderr,\
                      "methodCall: blocked unregistered method %s" % methodname
                return ''
        except (KeyError,AttributeError):
            return '' # RETURN FAILURE CODE
        return m(*args) # RUN THE OBJECT METHOD
    def serve_forever(self, demonize = True):
        'launch the XMLRPC service.  Never exits if demonize == True.'
        if demonize == True:
            print "Running as a daemon"
            detach_as_demon_process(self)
            serve_forever(self)
        else:
            print "Running in the background of active session"
            # Check if we're running interactively, as otherwise the server will
            # die right after starting. Two checks are needed for this: one for
            # a truly interactive session and one for the interpreter having
            # been run with the -i flag (makes the session interactive AFTER the
            # script has been executed). Unfortunately, the latter only works
            # with Python 2.6 and up.
            if not hasattr(sys, 'ps1'):
                if sys.version_info < (2, 6) or not sys.flags.interactive:
                    print "Warning: Running non-interactively without daemonising means the server will die right after starting. This is probably not what you want."
            thread.start_new_thread(serve_forever, (self, ))
    def register(self,url=None,name='index',server=None):
        'register our server with the designated index server'
        data=self.registrationData # RAISE ERROR IF NO DATA TO REGISTER...
        if server is None and url is not None: # USE THE URL TO GET THE INDEX SERVER
            server=get_connection(url,name)
        if server is not None:
            server.registerServer('%s:%d' % (self.host,self.port),data)
        else: # DEFAULT: SEARCH WORLDBASEPATH TO FIND INDEX SERVER
            from pygr import worldbase
            worldbase._mdb.registerServer('%s:%d' % (self.host,self.port),data)

class ResourceController(object):
    """Centralized controller for getting resources and rules for
    making them.
    """
    xmlrpc_methods={'load_balance':0,'setrule':0,'delrule':0,'report_load':0,
                    'register_coordinator':0,'unregister_coordinator':0,
                    'register_processor':0,'unregister_processor':0,
                    'get_resource':0,'acquire_rule':0,'release_rule':0,
                    'request_cpus':0,'retry_unused_hosts':0,
                    'get_status':0,'setthrottle':0,'del_lock':0,
                    'get_hostinfo':0,'set_hostinfo':0}
    _dispatch=safe_dispatch # RESTRICT XMLRPC TO JUST THE METHODS LISTED ABOVE
    max_tb=10
    def __init__(self,rc='controller',port=5000,overload_margin=0.6,
                 rebalance_frequency=1200,errlog=False,throttle=1.0):
        self.name=rc
        self.overload_margin=overload_margin
        self.rebalance_frequency=rebalance_frequency
        self.errlog=errlog
        self.throttle=throttle
        self.rebalance_time=time.time()
        self.must_rebalance=False
        self.host=get_hostname()
        self.hosts=FileDict(self.name+'.hosts',HostInfo)
        self.getrules()
        self.getresources()
        self.server,self.port = get_server(self.host,port)
        self.server.register_instance(self)
        self.coordinators={}
        self.njob=0
        self.locks={}
        self.systemLoad={}
        hostlist=[host for host in self.hosts]
        for host in hostlist: # 1ST ASSUME HOST EMPTY, THEN GET LOAD REPORTS
            hostFQDN=get_hostname(host) # CONVERT ALL HOSTNAMES TO FQDNs
            if hostFQDN!=host: # USE FQDN FOR ALL SUBSEQUENT REFS!
                self.hosts[hostFQDN]=self.hosts[host]
                del self.hosts[host]
            self.systemLoad[hostFQDN]=0.0

    __call__=serve_forever

    def assign_load(self):
        "calculate the latest balanced loads"
        maxload=0.
        total=0.
        current_job=99999999
        for c in self.coordinators.values():
            if c.priority>0.0 and c.job_id<current_job:
                current_job=c.job_id # FIND 1ST NON-ZER0 PRIORITY JOB
        for c in self.coordinators.values():
            if c.demand_ncpu: # DEMANDS A FIXED #CPUS, NO LOAD BALANCING
                c.run=True
            elif c.job_id==current_job or c.immediate:
                c.run=True # YES, RUN THIS JOB
                total+=c.priority
            else:
                c.run=False
        for v in self.hosts.values(): # SUM UP TOTAL CPUS
            maxload+=v.maxload
        maxload*=self.throttle # APPLY OUR THROTTLE CONTROL
        for c in self.coordinators.values(): #REMOVE DEMANDED CPUS
            if c.demand_ncpu:
                maxload-=c.demand_ncpu
        if maxload<0.: # DON'T ALLOW NEGATIVE VALUES
            maxload=0.
        if total>0.: # DON'T DIVIDE BY ZERO...
            maxload /= float(total)
        for c in self.coordinators.values(): # ALLOCATE SHARE OF TOTAL CPUS...
            if c.demand_ncpu: # ALLOCATE EXACTLY THE NUMBER REQUESTED
                c.allocated_ncpu=int(c.demand_ncpu)
            elif c.run: # COMPUTE BASED ON PRIORITY SHARE
                c.allocated_ncpu=int(maxload * c.priority)
            else: # NOT RUNNING
                c.allocated_ncpu=0
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def assign_processors(self):
        "hand out available processors to coordinators in order of need"
        margin=self.overload_margin-1.0
        free_cpus=[]
        nproc={}
        for c in self.coordinators.values(): # COUNT NUMBER OF PROCS
            for host,pid in c.processors: # RUNNING ON EACH HOST
                try:
                    nproc[host]+=1.0 # INCREMENT AN EXISTING COUNT
                except KeyError:
                    nproc[host]=1.0 # NEW, SO SET INITIAL COUNT
        for host in self.hosts: # BUILD LIST OF HOST CPUS TO BE ASSIGNED
            if host not in self.systemLoad: # ADDING A NEW HOST
                self.systemLoad[host]=0.0 # DEFAULT LOAD: ASSUME HOST EMPTY
            try: # host MAY NOT BE IN nproc, SO CATCH THAT ERROR
                if self.systemLoad[host]>nproc[host]:
                    raise KeyError # USE self.systemLoad[host]
            except KeyError:
                load=self.systemLoad[host] # MAXIMUM VALUE
            else:
                load=nproc[host] # MAXIMUM VALUE
            if load<self.hosts[host].maxload+margin:
                free_cpus+=int(self.hosts[host].maxload+self.overload_margin
                               -load)*[host]
        if len(free_cpus)==0: # WE DON'T HAVE ANY CPUS TO GIVE OUT
            return False
        l=[] # BUILD A LIST OF HOW MANY CPUS EACH COORDINATOR NEEDS
        for c in self.coordinators.values():
            ncpu=c.allocated_ncpu-len(c.processors)
            if ncpu>0:
                l+=ncpu*[c]  # ADD c TO l EXACTLY ncpu TIMES
        import random
        random.shuffle(l) # REORDER LIST OF COORDINATORS RANDOMLY
        i=0 # INDEX INTO OUR l LIST
        while i<len(free_cpus) and i<len(l): # HAND OUT THE FREE CPUS ONE BY ONE
            l[i].new_cpus.append(free_cpus[i])
            i+=1
        return i>0 # RETURN TRUE IF WE HANDED OUT SOME PROCESSORS

    def load_balance(self):
        "recalculate load assignments, and assign free cpus"
        self.rebalance_time=time.time() # RESET OUR FLAGS
        self.must_rebalance=False
        self.assign_load() # CALCULATE HOW MANY CPUS EACH COORDINATOR SHOULD GET
        self.assign_processors() # ASSIGN FREE CPUS TO COORDINATORS THAT NEED THEM
        for c in self.coordinators.values():
            c.update_load() # INFORM THE COORDINATOR
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def get_hostinfo(self,host,attr):
        "get a host attribute"
        return getattr(self.hosts[host],attr)

    def set_hostinfo(self,host,attr,val):
        "increase or decrease the maximum load allowed on a given host"
        try:
            setattr(self.hosts[host],attr,val)
        except KeyError:
            self.hosts[host]=HostInfo('%s=%s' %(attr,str(val)))
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def getrules(self):
        import shelve
        self.rules=dbfile.shelve_open(self.name+'.rules')

    def getresources(self):
        import shelve
        self.resources=dbfile.shelve_open(self.name+'.rsrc')

    def setrule(self,rsrc,rule):
        "save a resource generation rule into our database"
        self.rules[rsrc]=rule
        self.rules.close() # THIS IS THE ONLY WAY I KNOW TO FLUSH...
        self.getrules()
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def delrule(self,rsrc):
        "delete a resource generation rule from our database"
        try:
            del self.rules[rsrc]
        except KeyError:
            print >>sys.stderr, "Attempt to delete unknown resource rule %s" % rsrc
        else:
            self.rules.close() # THIS IS THE ONLY WAY I KNOW TO FLUSH...
            self.getrules()
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def setthrottle(self,throttle):
        "set the total level of usage of available CPUs, usually 1.0"
        self.throttle=float(throttle)
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def report_load(self,host,pid,load):
        "save a reported load from one of our processors"
        self.systemLoad[host]=load
        # AT A REGULAR INTERVAL WE SHOULD REBALANCE LOAD
        if self.must_rebalance or \
               time.time()-self.rebalance_time>self.rebalance_frequency:
            self.load_balance()
        if load<self.hosts[host].maxload+self.overload_margin:
            return True  # OK TO CONTINUE
        else:
            return False # THIS SYSTEM OVERLOADED, TELL PROCESSOR TO EXIT

    def register_coordinator(self,name,url,user,priority,resources,immediate,
                             demand_ncpu):
        "save a coordinator's registration info"
        try:
            print >>sys.stderr,'change_priority: %s (%s,%s): %f -> %f' \
                  % (name,user,url,self.coordinators[url].priority,priority)
            self.coordinators[url].priority=priority
            self.coordinators[url].immediate=immediate
            self.coordinators[url].demand_ncpu=demand_ncpu
        except KeyError:
            print >>sys.stderr,'register_coordinator: %s (%s,%s): %f' \
                  % (name,user,url,priority)
            self.coordinators[url]=CoordinatorInfo(name,url,user,priority,
                                                   resources,self.njob,immediate,
                                                   demand_ncpu)
            self.njob+=1 # INCREMENT COUNT OF JOBS WE'VE REGISTERED
        self.must_rebalance=True # FORCE REBALANCING ON NEXT OPPORTUNITY
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def unregister_coordinator(self,name,url,message):
        "remove a coordinator from our list"
        try:
            del self.coordinators[url]
            print >>sys.stderr,'unregister_coordinator: %s (%s): %s' \
                  % (name,url,message)
            self.load_balance() # FORCE IT TO REBALANCE THE LOAD TO NEW JOBS...
        except KeyError:
            print >>sys.stderr,'unregister_coordinator: %s unknown:%s (%s)' \
                  % (name,url,message)
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def request_cpus(self,name,url):
        "return a list of hosts for this coordinator to run processors on"
        try:
            c=self.coordinators[url]
        except KeyError:
            print >>sys.stderr,'request_cpus: unknown coordinator %s @ %s' % (name,url)
            return [] # HAND BACK AN EMPTY LIST
        self.assign_load() # CALCULATE HOW MANY CPUS EACH COORDINATOR SHOULD GET
        self.assign_processors() # ASSIGN FREE CPUS TO COORDINATORS THAT NEED THEM
        new_cpus=tuple(c.new_cpus) # MAKE A NEW COPY OF THE LIST OF HOSTS
        del c.new_cpus[:] # EMPTY OUR LIST
        return new_cpus

    def register_processor(self,host,pid,url):
        "record a new processor starting up"
        try:
            self.coordinators[url]+= (host,pid)
            self.systemLoad[host] += 1.0 # THIS PROBABLY INCREASES LOAD BY 1
        except KeyError:
            pass
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def unregister_processor(self,host,pid,url):
        "processor shutting down, so remove it from the list"
        try:
            self.coordinators[url]-= (host,pid)
            self.systemLoad[host] -= 1.0 # THIS PROBABLY DECREASES LOAD BY 1
            if self.systemLoad[host]<0.0:
                self.systemLoad[host]=0.0
            for k,v in self.locks.items(): # MAKE SURE THIS PROC HAS NO LOCKS...
                h=k.split(':')[0]
                if h==host and v==pid:
                    del self.locks[k] # REMOVE ALL ITS PENDING LOCKS
        except KeyError:
            pass
        self.load_balance() # FREEING A PROCESSOR, SO REBALANCE TO USE THIS
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def get_resource(self,host,pid,rsrc):
        """return a filename for the resource, or False if rule must be applied,
        or True if client must wait to get the resource"""
        key=host+':'+rsrc
        try: # JUST HAND BACK THE RESOURCE
            return self.resources[key]
        except KeyError:
            if key in self.locks:
                return True # TELL CLIENT TO WAIT
            else:
                return False # TELL CLIENT TO ACQUIRE IT VIA RULE

    def acquire_rule(self,host,pid,rsrc):
        "lock the resource on this specific host, and return its production rule"
        if rsrc not in self.rules:
            return False # TELL CLIENT NO SUCH RULE
        key=host+':'+rsrc
        if key in self.locks:
            return True # TELL CLIENT TO WAIT
        self.locks[key]=pid # LOCK THIS RESOURCE ON THIS HOST UNTIL CONSTRUCTED
        return self.rules[rsrc] # RETURN THE CONSTRUCTION RULE

    def release_rule(self,host,pid,rsrc):
        "client is done applying this rule, so now safe to give out the resource"
        key=host+':'+rsrc
        self.del_lock(host,rsrc)
        self.resources[key]=self.rules[rsrc][0] # ADD THE FILE NAME TO RESOURCE LIST
        self.resources.close() # THIS IS THE ONLY WAY I KNOW TO FLUSH THIS...
        self.getresources()
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def del_lock(self,host,rsrc):
        "delete a lock on a pending resource construction process"
        key=host+':'+rsrc
        try:
            del self.locks[key] # REMOVE THE LOCK
        except KeyError:
            print >>sys.stderr,"attempt to release non-existent lock %s,%s:%d" \
                  %(host,rule,pid)
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def retry_unused_hosts(self):
        "reset systemLoad for hosts that have no jobs running"
        myhosts={}
        for c in self.coordinators.values(): # LIST HOSTS WE'RE CURRENTLY USING
            for host,pid in c.processors:
                myhosts[host]=None # MARK THIS HOST AS IN USE
        for host in self.systemLoad: # RESET LOADS FOR ALL HOSTS WE'RE NOT USING
            if host not in myhosts:
                self.systemLoad[host]=0.0
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def get_status(self):
        """get report of system loads, max loads, coordinators, rules,
        resources, locks"""
        l=[(name,host.maxload) for name,host in self.hosts.items()]
        l.sort()
        return self.name,self.errlog,self.systemLoad,l,\
               [(c.name,c.url,c.priority,c.allocated_ncpu,len(c.processors),\
                 c.start_time) for c in self.coordinators.values()], \
                 dict(self.rules),dict(self.resources),self.locks

class AttrProxy(object):
    def __init__(self,getattr_proxy,k):
        self.getattr_proxy=getattr_proxy
        self.k=k
    def __getattr__(self,attr):
        try:
            val=self.getattr_proxy(self.k,attr) # GET IT FROM OUR PROXY
        except:
            raise AttributeError('unable to get proxy attr '+attr)
        setattr(self,attr,val) # CACHE THIS ATTRIBUTE RIGHT HERE!
        return val

class DictAttrProxy(dict):
    def __init__(self,getattr_proxy):
        dict.__init__(self)
        self.getattr_proxy=getattr_proxy
    def __getitem__(self,k):
        try:
            return dict.__getitem__(self,k)
        except KeyError:
            val=AttrProxy(self.getattr_proxy,k)
            self[k]=val
            return val

class Coordinator(object):
    """Run our script as Processor on one or more client nodes, using
    XMLRPC communication between clients and server.
    On the server all output is logged to name.log,
    and successfully completed task IDs are stored in name.success,
    and error task IDs are stored in name.error
    On the clients all output is logged to the file name_#.log in the user's
    and/or system-specific temporary directory."""
    xmlrpc_methods={'start_processors':0,'register_client':0,'unregister_client':0,
                    'report_success':0,'report_error':0,'next':0,
                    'get_status':0,'set_max_clients':0,'stop_client':0}
    _dispatch=safe_dispatch # RESTRICT XMLRPC TO JUST THE METHODS LISTED ABOVE
    max_tb=10 # MAXIMUM #STACK LEVELS TO PRINT IN TRACEBACKS
    max_ssh_errors=5 #MAXIMUM #ERRORS TO PERMIT IN A ROW BEFORE QUITTING
    python='python' # DEFAULT EXECUTABLE FOR RUNNING OUR CLIENTS
    def __init__(self,name,script,it,resources,port=8888,priority=1.0,rc_url=None,
                 errlog=False,immediate=False,ncpu_limit=999999,
                 demand_ncpu=0,max_initialization_errors=3,**kwargs):
        self.name=name
        self.script=script
        self.it=iter(it) # MAKE SURE it IS AN ITERATOR; IF IT'S NOT, MAKE IT SO
        self.resources=resources
        self.priority=priority
        self.errlog=errlog
        self.immediate=immediate
        self.ncpu_limit=ncpu_limit
        self.demand_ncpu=demand_ncpu
        self.max_initialization_errors=max_initialization_errors
        self.kwargs=kwargs
        self.host=get_hostname()
        self.user=os.environ['USER']
        try: # MAKE SURE ssh-agent IS AVAILABLE TO US BEFORE LAUNCHING LOTS OF PROCS
            a=os.environ['SSH_AGENT_PID']
        except KeyError:
            raise OSError(1,'SSH_AGENT_PID not found.  No ssh-agent running?')
        self.dir=os.getcwd()
        self.n=0
        self.nsuccess=0
        self.nerrors=0
        self.nssh_errors=0
        self.iclient=0
        self.max_clients=40
        if rc_url is None: # USE DEFAULT RESOURCE CONTROLLER ADDRESS ON SAME HOST
            rc_url='http://%s:5000' % self.host
        self.rc_url=rc_url
        self.rc_server=xmlrpclib.ServerProxy(rc_url) #GET CONNECTION TO RESOURCE CONTROLLER
        self.server,self.port = get_server(self.host,port) #CREATE XMLRPC SERVER
        self.server.register_instance(self) # WE PROVIDE ALL THE METHODS FOR THE SERVER
        self.clients={}
        self.pending={}
        self.already_done={}
        self.stop_clients={}
        self.logfile={}
        self.clients_starting={}
        self.clients_initializing={}
        self.initialization_errors={}
        try: # LOAD LIST OF IDs ALREADY SUCCESSFULLY PROCESSED, IF ANY
            f=file(name+'.success','rU') # text file
            for line in f:
                self.already_done[line.strip()]=None
            f.close()
        except IOError: # OK IF NO SUCCESS FILE YET, WE'LL CREATE ONE.
            pass
        self.successfile=file(name+'.success','a') # success FILE IS CUMMULATIVE
        self.errorfile=file(name+'.error','w') # OVERWRITE THE ERROR FILE
        self.done=False
        self.hosts=DictAttrProxy(self.rc_server.get_hostinfo)
        self.register()

    def __call__(self,*l,**kwargs):
        "start the server, and launch a cpu request in a separate thread"
        import threading
        t=threading.Thread(target=self.initialize_thread)
        t.start()
        serve_forever(self,*l,**kwargs)

    def initialize_thread(self):
        "run this method in a separate thread to bootstrap our initial cpu request"
        time.sleep(5) # GIVE serve_forever() TIME TO START SERVER
        self.rc_server.load_balance() # NOW ASK CONTROLLER TO REBALANCE AND GIVE US CPUS

    def start_client(self,host):
        "start a processor on a client node"
        import tempfile
        if len(self.clients)>=self.ncpu_limit:
            print >>sys.stderr,'start_client: blocked, CPU limit', \
                  len(self.clients),self.ncpu_limit
            return # DON'T START ANOTHER PROCESS, TOO MANY ALREADY
        if len(self.clients)>=self.max_clients:
            print >>sys.stderr,'start_client: blocked, too many already', \
                  len(self.clients),self.max_clients
            return # DON'T START ANOTHER PROCESS, TOO MANY ALREADY
        try:
            if len(self.clients_starting[host])>=self.max_ssh_errors:
                print >>sys.stderr,\
                      'start_client: blocked, too many unstarted jobs:',\
                      host,self.clients_starting[host]
                return # DON'T START ANOTHER PROCESS, host MAY BE DEAD...
        except KeyError: # NO clients_starting ON host, GOOD!
            pass
        try:
            if len(self.initialization_errors[host])>=self.max_initialization_errors:
                print >>sys.stderr,\
                      'start_client: blocked, too many initialization errors:',\
                      host,self.initialization_errors[host]
                return # DON'T START ANOTHER PROCESS, host HAS A PROBLEM
        except KeyError: # NO initialization_errors ON host, GOOD!
            pass
        try:
            sshopts=self.hosts[host].sshopts # GET sshopts VIA XMLRPC
        except AttributeError:
            sshopts=''
        logfile=os.path.join(tempfile.gettempdir(), '%s_%d.log' % (self.name, self.iclient))
        # PASS OUR KWARGS ON TO THE CLIENT PROCESSOR
        kwargs=' '.join(['--%s=%s'%(k,v) for k,v in self.kwargs.items()])
        cmd='cd %s;%s %s --url=http://%s:%d --rc_url=%s --logfile=%s %s %s' \
             % (self.dir,self.python,self.script,self.host,self.port,
                self.rc_url,logfile,self.name,kwargs)
        # UGH, HAVE TO MIX CSH REDIRECTION (REMOTE) WITH SH REDIRECTION (LOCAL)
        ssh_cmd="ssh %s %s '(%s) </dev/null >&%s &' </dev/null >>%s 2>&1 &" \
                 % (sshopts,host,cmd,logfile,self.errlog)
        print >>sys.stderr,'SSH: '+ssh_cmd
        self.logfile[logfile]=[host,False,self.iclient] # NO PID YET
        try: # RECORD THIS CLIENT AS STARTING UP
            self.clients_starting[host][self.iclient]=time.time()
        except KeyError: # CREATE A NEW HOST ENTRY
            self.clients_starting[host]={self.iclient:time.time()}
        # RUN SSH IN BACKGROUND TO AVOID WAITING FOR IT TO TIMEOUT!!!
        os.system(ssh_cmd) # LAUNCH THE SSH PROCESS, SHOULD RETURN IMMEDIATELY
        self.iclient += 1 # ADVANCE OUR CLIENT COUNTER

    def start_processors(self,hosts):
        "start processors on the list of hosts using SSH transport"
        for host in hosts: # LAUNCH OURSELVES AS PROCESSOR ON ALL THESE HOSTS
            self.start_client(host)
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def register(self):
        "register our existence with the resource controller"
        url='http://%s:%d' % (self.host,self.port)
        self.rc_server.register_coordinator(self.name,url,self.user,
                                            self.priority,self.resources,
                                            self.immediate,self.demand_ncpu)

    def unregister(self,message):
        "tell the resource controller we're exiting"
        url='http://%s:%d' % (self.host,self.port)
        self.rc_server.unregister_coordinator(self.name,url,message)

    def register_client(self,host,pid,logfile):
        'XMLRPC call to register client hostname and PID as starting_up'
        print >>sys.stderr,'register_client: %s:%d' %(host,pid)
        self.clients[(host,pid)]=0
        try:
            self.logfile[logfile][1]=pid # SAVE OUR PID
            iclient=self.logfile[logfile][2] # GET ITS CLIENT ID
            del self.clients_starting[host][iclient] #REMOVE FROM STARTUP LIST
        except KeyError:
            print >>sys.stderr,'no client logfile?',host,pid,logfile
        self.clients_initializing[(host,pid)]=logfile
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def unregister_client(self,host,pid,message):
        'XMLRPC call to remove client from register as exiting'
        print >>sys.stderr,'unregister_client: %s:%d %s' % (host,pid,message)
        try:
            del self.clients[(host,pid)]
        except KeyError:
            print >>sys.stderr,'unregister_client: unknown client %s:%d' % (host,pid)
        try: # REMOVE IT FROM THE LIST OF CLIENTS TO SHUTDOWN, IF PRESENT
            del self.stop_clients[(host,pid)]
        except KeyError:
            pass
        try: # REMOVE FROM INITIALIZATION LIST
            del self.clients_initializing[(host,pid)]
        except KeyError:
            pass
        if len(self.clients)==0 and self.done: # NO MORE TASKS AND NO MORE CLIENTS
            self.exit("Done") # SO SERVER CAN EXIT
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def report_success(self,host,pid,success_id):
        'mark task as successfully completed'
        print >>self.successfile,success_id # KEEP PERMANENT RECORD OF SUCCESS ID
        self.successfile.flush()
        self.nsuccess += 1
        try:
            self.clients[(host,pid)] += 1
        except KeyError:
            print >>sys.stderr,'report_success: unknown client %s:%d' % (host,pid)
        try:
            del self.pending[success_id]
        except KeyError:
            print >>sys.stderr,'report_success: unknown ID %s' % str(success_id)
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def report_error(self,host,pid,id,tb_report):
        "get traceback report from client as text"
        print >>sys.stderr,"TRACEBACK: %s:%s ID %s\n%s" % \
              (host,str(pid),str(id),tb_report)
        if (host,pid) in self.clients_initializing:
            logfile=self.clients_initializing[(host,pid)]
            try:
                self.initialization_errors[host].append(logfile)
            except KeyError:
                self.initialization_errors[host]=[logfile]
        try:
            del self.pending[id]
        except KeyError: # NOT ASSOCIATED WITH AN ACTUAL TASK ID, SO DON'T RECORD
            if id is not None and id is not False:
                print >>sys.stderr,'report_error: unknown ID %s' % str(id)
        else:
            print >>self.errorfile,id # KEEP PERMANENT RECORD OF FAILURE ID
            self.nerrors+=1
            self.errorfile.flush()
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE

    def next(self,host,pid,success_id):
        'return next ID from iterator to the XMLRPC caller'
        if (host,pid) not in self.clients:
            print >>sys.stderr,'next: unknown client %s:%d' % (host,pid)
            return False # HAND BACK "NO MORE FOR YOU TO DO" SIGNAL
        try: # INITIALIZATION DONE, SO REMOVE FROM INITIALIZATION LIST
            del self.clients_initializing[(host,pid)]
        except KeyError:
            pass
        if success_id is not False:
            self.report_success(host,pid,success_id)
        if self.done: # EXHAUSTED OUR ITERATOR, SO SHUT DOWN THIS CLIENT
            return False # HAND BACK "NO MORE FOR YOU TO DO" SIGNAL
        try:  # CHECK LIST FOR COMMAND TO SHUT DOWN THIS CLIENT
            del self.stop_clients[(host,pid)] # IS IT IN stop_clients?
            return False # IF SO, HAND BACK "NO MORE FOR YOU TO DO" SIGNAL
        except KeyError: # DO ONE MORE CHECK: ARE WE OVER OUR MAX ALLOWED LOAD?
            if len(self.clients)>self.max_clients: # YES, BETTER THROTTLE DOWN
                print >>sys.stderr,'next: halting %s:too many processors (%d>%d)' \
                      % (host,len(self.clients),self.max_clients)
                return False # HAND BACK "NO MORE FOR YOU TO DO" SIGNAL
        for id in self.it: # GET AN ID WE CAN USE
            if str(id) not in self.already_done:
                self.n+=1 # GREAT, WE CAN USE THIS ID
                self.lastID=id
                self.pending[id]=(host,pid,time.time())
                print >>sys.stderr,'giving id %s to %s:%d' %(str(id),host,pid)
                return id
        print >>sys.stderr,'exhausted all items from iterator!'
        self.done=True # EXHAUSTED OUR ITERATOR
        self.priority=0.0 # RELEASE OUR CLAIMS ON ANY FURTHER PROCESSOR ALLOCATION
        self.register() # AND INFORM THE RESOURCE CONTROLLER
        return False # False IS CONFORMABLE BY XMLRPC...

    def get_status(self):
        "return basic status info on number of jobs finished, client list etc."
        client_report=[client+(nsuccess,) for client,nsuccess in self.clients.items()]
        pending_report=[(k,)+v for k,v in self.pending.items()]
        return self.name,self.errlog,self.n,self.nsuccess,self.nerrors,client_report,\
               pending_report,self.logfile
    def set_max_clients(self,n):
        "change the maximum number of clients we should have running"
        self.max_clients=int(n)  # MAKE SURE n IS CONVERTABLE TO int
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE
    def stop_client(self,host,pid):
        "set signal forcing this client to exit on next iteration"
        self.stop_clients[(host,pid)]=None
        return True  # USE THIS AS DEFAULT XMLRPC RETURN VALUE
    def exit(self,message):
        "clean up and close this server"
        self.unregister(message)
        self.successfile.close()
        self.errorfile.close()
        sys.exit()

try:
    class ResourceFile(file):
        """wrapper around some locking behavior, to ensure only one copy operation
        performed for a given resource on a given host.
        Otherwise, it's just a regular file object."""
        def __init__(self,resource,rule,mode,processor):
            "resource is name of the resource; rule is (localFile,cpCommand)"
            self.resource=resource
            self.processor=processor
            localFile,cpCommand=rule
            if not os.access(localFile,os.R_OK):
                cmd=cpCommand % localFile
                print 'copying data:',cmd
                os.system(cmd)
            file.__init__(self,localFile,mode) # NOW INITIALIZE AS A REAL FILE OBJECT

        def close(self):
            self.processor.release_rule(self.resource) # RELEASE THE LOCK WE PLACED ON THIS RULE
            file.close(self)
except TypeError:
    pass

class Processor(object):
    'provides an iterator interface to an XMLRPC ID server'
    max_errors_in_a_row=10 # LOOKS LIKE NOTHING WORKS HERE, SO QUIT!
    max_tb=10 # DON'T SHOW MORE THAN 10 STACK LEVELS FOR A TRACEBACK
    report_frequency=600
    overload_max=5 # MAXIMUM NUMBER OF OVERLOAD EVENTS IN A ROW BEFORE WE EXIT
    def __init__(self,url="http://localhost:8888",
                 rc_url='http://localhost:5000',logfile=False,**kwargs):
        self.url=url
        self.logfile=logfile
        self.server=xmlrpclib.ServerProxy(url)
        self.rc_url=rc_url
        self.rc_server=xmlrpclib.ServerProxy(rc_url)
        self.host=get_hostname()
        self.pid=os.getpid()
        self.user=os.environ['USER']
        self.success_id=False
        self.pending_id=False
        self.exit_message='MYSTERY-EXIT please debug'
        self.overload_count=0

    def register(self):
        "add ourselves to list of processors for this server"
        self.server.register_client(self.host,self.pid,self.logfile)
        self.rc_server.register_processor(self.host,self.pid,self.url)
        print >>sys.stderr,'REGISTERED:',self.url,self.rc_url

    def unregister(self,message):
        "remove ourselves from list of processors for this server"
        if self.success_id is not False: # REPORT THAT LAST JOB SUCCEEDED!
            self.report_success(self.success_id)
        self.server.unregister_client(self.host,self.pid,message)
        self.rc_server.unregister_processor(self.host,self.pid,self.url)
        print >>sys.stderr,'UNREGISTERED:',self.url,self.rc_url,message

    def __iter__(self):
        return self

    def next(self):
        "get next ID from server"
        # REPORT LAST JOB SUCCESSFULLY COMPLETED, IF ANY
        while 1:
            id=self.server.next(self.host,self.pid,self.success_id)
            self.success_id=False # ERASE SUCCESS ID
            if id is True: # WE'RE BEING TOLD TO JUST WAIT
                time.sleep(60) # SO GO TO SLEEP FOR A MINUTE
            else:
                break
        if id is False: # NO MODE id FOR US TO PROCESS, SO QUIT
            self.serverStopIteration=True # RECORD THIS AS GENUINE END EVENT
            raise StopIteration
        else: # HAND BACK THE id TO THE USER
            self.pending_id=id
            return id

    def report_success(self,id):
        "report successful completion of task ID"
        self.server.report_success(self.host,self.pid,id)

    def report_error(self,id):
        "report an error using traceback.print_exc()"
        import StringIO
        err_report=StringIO.StringIO()
        traceback.print_exc(self.max_tb,sys.stderr) #REPORT TB TO OUR LOG
        traceback.print_exc(self.max_tb,err_report) #REPORT TB TO SERVER
        self.server.report_error(self.host,self.pid,id,err_report.getvalue())
        err_report.close()

    def report_load(self):
        "report system load"
        load=os.getloadavg()[0] # GET 1 MINUTE LOAD AVERAGE
        if self.rc_server.report_load(self.host,self.pid,load) is False:
            self.overload_count+=1 # ARE WE CONSISTENTLY OVERLOADED FOR EXTENDED PERIOD?
            if self.overload_count>self.overload_max: # IF EXCEEDED LIMIT, EXIT
                self.exit('load too high')
        else:
            self.overload_count=0

    def open_resource(self,resource,mode):
        "get a file object for the requested resource, opened in mode"
        while 1:
            rule=self.rc_server.get_resource(self.host,self.pid,resource)
            if rule is False: # WE HAVE TO LOCK AND APPLY A RULE...
                rule=self.acquire_rule(resource)
                if rule is True: # HMM, LOOKS LIKE A RACE CONDITION. KEEP WAITING
                    time.sleep(60)  # WAIT A MINUTE BEFORE ASKING FOR RESOURCE AGAIN
                    continue
                return ResourceFile(resource,rule,mode,self) #CONSTRUCT THE RESOURCE
            elif rule is True: # RULE IS LOCKED BY ANOTHER PROCESSOR
                time.sleep(60)  # WAIT A MINUTE BEFORE ASKING FOR RESOURCE AGAIN
            else: # GOT A REGULAR FILE, SO JUST OPEN IT
                return file(rule,mode)

    def acquire_rule(self,resource):
        "lock the specified resource rule for this host, so it's safe to build it"
        rule=self.rc_server.acquire_rule(self.host,self.pid,resource)
        if rule is False: # NO SUCH RESOURCE?!?
            self.exit('invalid resource: '+resource)
        return rule

    def release_rule(self,resource):
        "release our lock on this resource rule, so others can use it"
        self.rc_server.release_rule(self.host,self.pid,resource)

    def exit(self,message):
        "save message for self.unregister() and force exit"
        self.exit_message=message
        raise SystemExit

    def run_all(self,resultGenerator,**kwargs):
        "run until all task IDs completed, trap & report all errors"
        errors_in_a_row=0
        it=resultGenerator(self,**kwargs) # GET ITERATOR FROM GENERATOR
        report_time=time.time()
        self.register() # REGISTER WITH RESOURCE CONTROLLER & COORDINATOR
        initializationError=None
        try: # TRAP ERRORS BOTH IN USER CODE AND coordinator CODE
            while 1:
                try: # TRAP AND REPORT ALL ERRORS IN USER CODE
                    id=it.next() # THIS RUNS USER CODE FOR ONE ITERATION
                    self.success_id=id  # MARK THIS AS A SUCCESS...
                    errors_in_a_row=0
                    initializationError=False
                except StopIteration: # NO MORE TASKS FOR US...
                    if not hasattr(self,'serverStopIteration'): # WIERD!!
                        # USER CODE RAISED StopIteration?!?
                        self.report_error(self.pending_id) # REPORT THE PROBLEM
                        self.exit_message='user StopIteration error'
                    elif initializationError:
                        self.exit_message='initialization error'
                    else:
                        self.exit_message='done'
                    break
                except SystemExit: # sys.exit() CALLED
                    raise  # WE REALLY DO WANT TO EXIT.
                except: # MUST HAVE BEEN AN ERROR IN THE USER CODE
                    if initializationError is None: # STILL IN INITIALIZATION
                        initializationError=True
                    self.report_error(self.pending_id) # REPORT THE PROBLEM
                    errors_in_a_row +=1
                    if errors_in_a_row>=self.max_errors_in_a_row:
                        self.exit_message='too many errors'
                        break
                if time.time()-report_time>self.report_frequency:
                    self.report_load() # SEND A ROUTINE LOAD REPORT
                    report_time=time.time()
        except SystemExit: # sys.exit() CALLED
            pass  # WE REALLY DO WANT TO EXIT.
        except: # IMPORTANT TO TRAP ALL ERRORS SO THAT WE UNREGISTER!!
            traceback.print_exc(self.max_tb,sys.stderr) #REPORT TB TO OUR LOG
            self.exit_message='error trap'
        self.unregister('run_all '+self.exit_message) # MUST UNREGISTER!!

    def run_interactive(self,it,n=1,**kwargs):
        "run n task IDs, with no error trapping"
        if not hasattr(it,'next'):
            it=it(self,**kwargs) # ASSUME it IS GENERATOR, USE IT TO GET ITERATOR
        i=0
        self.register() # REGISTER WITH RESOURCE CONTROLLER & COORDINATOR
        try: # EVEN IF ERROR OCCURS, WE MUST UNREGISTER!!
            for id in it:
                self.success_id=id
                i+=1
                if i>=n:
                    break
        except:
            self.unregister('run_interactive error') # MUST UNREGISTER!!!
            raise # SHOW THE ERROR INTERACTIVELY
        self.unregister('run_interactive exit')
        return it # HAND BACK ITERATOR IN CASE USER WANTS TO RUN MORE...

def parse_argv():
    "parse sys.argv into a dictionary of GNU-style args --foo=bar and list of other args"
    d={}
    l=[]
    for v in sys.argv[1:]:
        if v[:2]=='--':
            try:
                k,v=v[2:].split('=')
                d[k]=v
            except ValueError:
                d[v[2:]]=None
        else:
            l.append(v)
    return d,l

def start_client_or_server(clientGenerator,serverGenerator,resources,script):
    """start controller, client or server depending on whether
    we get coordinator argument from the command-line args.

    Client must be a generator function that takes Processor as argument,
    and uses it as an iterator.
    Also, clientGenerator must yield the IDs that the Processor provides
    (this structure allows us to trap all exceptions from clientGenerator,
    while allowing it to do resource initializations that would be
    much less elegant in a callback function.)

    Server must be a function that returns an iterator (e.g. a generator).
    Resources is a list of strings naming the resources we need
    copied to local host for client to be able to do its work.

    Both client and server constructors use **kwargs to get command
    line arguments (passed as GNU-style --foo=bar;
    see the constructor arguments to see the list of
    options that each can be passed.

    #CALL LIKE THIS FROM yourscript.py:
    import coordinator
    if __name__=='__main__':
      coordinator.start_client_or_server(clientGen,serverGen,resources,__file__)

    To start the resource controller:
      python coordinator.py --rc=NAME [options]

    To start a job coordinator:
      python yourscript.py NAME [--rc_url=URL] [options]

    To start a job processor:
      python yourscript.py --url=URL --rc_url=URL [options]"""
    d,l=parse_argv()
    if 'url' in d: # WE ARE A CLIENT!
        client=Processor(**d)
        time.sleep(5) # GIVE THE SERVER SOME BREATHING SPACE
        client.run_all(clientGenerator,**d)
    elif 'rc' in d: # WE ARE THE RESOURCE CONTROLLER
        rc_server=ResourceController(**d) # NAME FOR THIS CONTROLLER...
        detach_as_demon_process(rc_server)
        rc_server() # START THE SERVER
    else: # WE ARE A SERVER
        try: # PASS OUR KWARGS TO THE SERVER FUNCTION
            it=serverGenerator(**d)
        except TypeError: # DOESN'T WANT ANY ARGS?
            it=serverGenerator()
        server=Coordinator(l[0],script,it,resources,**d)
        detach_as_demon_process(server)
        server() # START THE SERVER

class CoordinatorMonitor(object):
    "Monitor a Coordinator."
    def __init__(self,coordInfo):
        self.name,self.url,self.priority,self.allocated_ncpu,self.ncpu,\
                 self.start_time=coordInfo
        self.server=xmlrpclib.ServerProxy(self.url)
        self.get_status()
    def get_status(self):
        self.name,self.errlog,self.n,self.nsuccess,self.nerrors,self.client_report,\
               self.pending_report,self.logfile=self.server.get_status()
        print "Got status from Coordinator:",self.name,self.url
    def __getattr__(self,attr):
        "just pass on method requests to our server"
        return getattr(self.server,attr)

class RCMonitor(object):
    """monitor a ResourceController.  Useful methods:
    get_status()
    load_balance()
    setrule(rsrc,rule)
    delrule(rsrc)
    setload(host,maxload)
    retry_unused_hosts()
    Documented in ResourceController docstrings."""
    def __init__(self,host=None,port=5000):
        host=get_hostname(host) # GET FQDN
        self.rc_url='http://%s:%d' %(host,port)
        self.rc_server=xmlrpclib.ServerProxy(self.rc_url)
        self.get_status()

    def get_status(self):
        self.name,self.errlog,self.systemLoad,self.hosts,coordinators, \
               self.rules,self.resources,self.locks=self.rc_server.get_status()
        print "Got status from ResourceController:",self.name,self.rc_url
        self.coordinators={}
        for cinfo in coordinators:
            try: # IF COORDINATOR HAS DIED, STILL WANT TO RETURN RCMonitor...
                self.coordinators[cinfo[0]]=CoordinatorMonitor(cinfo)
            except socket.error,e: # JUST COMPLAIN, BUT CONTINUE...
                print >>sys.stderr,"Unable to connect to coordinator:",cinfo,e

    def __getattr__(self,attr):
        "just pass on method requests to our rc_server"
        return getattr(self.rc_server,attr)

def test_client(server,**kwargs):
    for id in server:
        print 'ID',id
        yield id
        time.sleep(1)

def test_server():
    return range(1000)

if __name__=='__main__':
    start_client_or_server(test_client,test_server,[],__file__)

redpajama_set_name: RedPajamaGithub


It is believed due to an incident where someone was burned due to the high flammability of celluloid, a decision was made to eliminate celluloid and use a different material for credentials. 1932 is a transition year for credentials as the type changes from being primarily celluloid or pin back button to almost entirely cardboard/pasteboard with a few exceptions noted below.
Cardboard/pasteboard race day official credentials are blank on the back from 1932 through 1934 but become two sided in 1935 with the same design on both sides.
From 1936 through 1946, a serial number appears on one side of all cardboard/pasteboard credentials.
Season track passes are two sided with a design on one side and an information area to be filled in on the back.
Temporary track passes generally have a design on the front and at least* three paper tear off pages on the back. On some years the last page is either glued or has stuck to the back of the pass however, it is *believed that over time this has occurred and these were not originally made like this.
The different color and number on the Pit pin back buttons may be to differentiate the *pit box number.
From 1938 through 1946, metal pit badge credentials were issued along with cardboard/pasteboard credentials. Please refer to the pit badges page for more information regarding pit badges.
Note the "Fyr-Fyter" credential is of the thin cardboard type as are those of 1934 and 1937. Note also the #4 button on the Guest Race Day Official Credential. The numbers (which can be on a button or printed on the credential), are believed to designate a floor of the Pagoda. Most are found on "Guest" credentials, but there are examples other than "Guest" with the #2 1946 "Pagoda Paddock" being one of them.
It is not certain what type of material was used to make the Ticket Seller credential below, but it *may be of a medium to heavy paper similar to the 1936 Lap Scorer below or a cloth/canvas type material like the 1937 AAA Time Trial below.
The Newspaper Boys of America pin back button below is not believed to be issued by the Speedway, but by the date, it was obviously used on race day.
The race day/race day official Pit below is the *last known pin back button type issued by the Speedway. The Lap Scorer & Guard ribbons below and the 1940 Guard ribbon below are believed to be *made of a medium to heavy paper. As these are the only examples from pre-World War II (there are examples after WWII), it is *assumed that this type was used in other years prior to 1941.
The AAA Time Trial credential below is made of the same cloth/canvas type material as the 1937 Time Trial Armband (see the IMS Armbands 1909-1947). Why it is blue on one side and white on the other is unknown.
Note that one of the secondary race day credentials below has a blank space where a designation could be written.
Credentials from 1932 through 1946 do become easier to find but again, none can be considered common. Those from 1937 on up tend to be easier to find and as with previous credentials, those associated with race teams are more desirable.


Red Pique Fabric – Tomato. 100% cotton. 60″ fabric with. We also have this in a slightly lighter shade of red, Red Pique.
New Customers: we sell pique fabric wholesale: please see here for pricing and ordering information: Wholesale Account Information. However, if you're looking to buy our pique fabrics retail, please see our list of Fabric Finder's Inc. Retailers.


It's (finally, almost) here! Starting next week, the Eternal Community Store will be available in-game (PC and Mac builds only)!
Obviously, our 'Initial Test' of Interactive Twitch Drops took (ahem) a little longer than we anticipated, but along the way we collected a lot of data, looking at various types of streamer and viewer activity and accumulation rates, and ultimately tweaked some of our initial assumptions.
So it starts with our content creators. Our primary goal with this project was to create an ecosystem that supports our streaming community in meaningful ways, but also in ways that don't undermine Eternal's base economy, and that aren't able to be easily abused. We've heard a lot of stories about how folks 'came for the drops, but stayed for the show,' and we want to continue to promote that dynamic, connecting viewers with streamers, and also walking the tight rope of balancing engagement and rewards.
So first of all, thanks for your patience! As we've said previously, the whole thing is pretty experimental, and we're sure there will be hiccups and tweaks and adjustments as we go (and just as a disclaimer, everything about this system is subject to change).
And after such a long delay, we also understand that expectations are not likely to be lined up with what we deliver, so please continue giving feedback, and please keep it constructive. The Community Store is meant to be a fun value-add that evolves as for engaging with Eternal outside of the game, and it's important that we continue to hear from you as it evolves along the way.
In the Community Store, you'll be able to spend the Influence you earn from watching your favorite streamers on Twitch to buy Packs of cards and cosmetic items like avatars, totems, card backs, and new emote packages.
For streamers, the Community Store lets you turn the Influence you've earned on stream into channel-exclusive Twitch Drops campaigns to help your audience grow! And for our most dedicated streamers, we're offering some longer term stretch goal experiences, such as working with the DWD team to design a card or a card back!
Interactive Twitch Drops have been pumping out Influence for a while through bugs, rate changes, and design changes. So when you log in after the Community Store goes live, you will find an Influence balance in-game that reflects the current design mapped to your activity since the program kicked off. In short, rather than inflating all Community Store Influence prices to account for the delay, we've reduced the balances so that we can work with more reasonable reward costs and round numbers.
Instead of a max of 8 hours of day of Influence value for streaming and viewing, we've dialed the caps back to 4 hours per day so as not to incentivize extreme behavior. We've also reduced the number of Interactive Drops chests that appear for viewers from approximately 6 per hour to 3 per hour.
As we stated up front, Droptober wasn't ultimately intended to become Dropsgiving, Dropsmas, and Happy Drops Year, all rolled into one – we had been planning to eliminate always-on passive drops all together, and only use them for special events, like ECQs. However, we're going to continue for the time being with a low-value pool of primarily commons and uncommons (with very rare chances for higher value items) as a background for the whole program.
One of the difficulties of a system like this is making it abuse-proof, both on the streaming side and on the viewing side. (We've seen and hopefully taken care of a number of 24/7 and other types of fraudulent streamers, so far.) The goal of Interactive Drops (instead of the usual Passive Drop system) is to create a system where people aren't just AFKing on both sides of the ball. If we detect bots or see unrealistic interaction rates, we'll take action, by wiping partial or complete Influence balances, disabling access to the Influence Store, or even suspending or banning Eternal accounts if necessary.
To help Twitch streamers continue to grow their audiences, players will be able to purchase special Drops Campaigns that are only active on their channel!
Drops Campaigns come in four tiers, from Bronze to Diamond.
Individual Drops include cards, premium cards, Draft Tickets, and Packs, with higher-tier Campaigns containing better Drops.
Stockpile Bundle – 30 Packs from the first three sets of Eternal!
Collaborate with the Dire Wolf art team on a premium card back to be released in a future Eternal product!
Collaborate with the Dire Wolf design team on a card to be released in a future Eternal product!
Stay tuned next week for the final details on pricing. There's more to come in the Eternal Community Store, and we'll be introducing new products (and retiring old ones) over time. So if you've been thinking about trying your hand at streaming, or just want to support your favorite content creators, now's the perfect time to join the Eternal community on Twitch!
And as always, let us know what you think, and especially let us know if you've got some ideas for cool things to put in the Community Store yourselves.


text:
What's The Difference Between The West Bank and The Gaza Strip?
By Alessandria Masi @alessandriamasi
An Israeli rocket is fired into the northern Gaza Strip Thursday. Reuters/Amir Cohen
When Israel invaded the Gaza Strip Thursday, no military operation was happening in the other part of the Palestinian territories, the West Bank. The two territories have had drastically different relationships with Israel and the rest of the world.
In the Gaza Strip, the government is led by the radical Islamist movement Hamas. In the West Bank, Fatah, a political party formerly known as the Palestinian National Liberation Movement, is in power. Both Hamas and Fatah are predominantly Sunni Muslim groups. Israel considers the leadership of the West Bank to be its legitimate negotiating partner for the Palestinian territories.
While both groups were created with the goal of reclaiming land from Israel, Fatah has accepted the existence of Israel, but Hamas hasn't. Hamas, headed by Khaled Mashal, is considered a terrorist organization by Israel, the U.S., Canada, Egypt and Jordan, as well as the European Union. Israel refuses to consider Hamas a legitimate government, and Hamas refuses to acknowledge the State of Israel.
Until recently, Fatah also refused to recognize Hamas as a legitimate government. In April 2014, however, the two Palestinian factions announced a reconciliation agreement, as BBC News reported. Fatah leader Mahmoud Abbas, who also heads the Palestinian National Authority, condemned Israel's attacks on the Gaza Strip, calling them a "genocide." Speaking in the West Bank last week, he said, "What's happening now is a war against the Palestinian people as a whole and not against the [militant] factions."
The map of the Palestinian territories came to look the way it does today because of a series of wars, accords, uprisings and violent clashes. Below is a timeline of major events.
In the 1967 Six-Day War, aka the Arab-Israeli War, Israel defeated Egypt, Jordan and Syria. Among other territory, it then occupied the West Bank, the Gaza Strip and East Jerusalem, as well as the Sinai Peninsula in Egypt. Palestinians had lived on much of the land taken by Israel.
In 1978, Egyptian President Anwar Sadat and Israeli Prime Minister Menachem Begin signed the Camp David Accords, promoted by U.S. President Jimmy Carter. It was the first peace treaty signed between Israel and an Arab country. One of the accord's conditions was that an autonomous government be set up in the Gaza Strip and the West Bank. Once that was established, Israeli troops had to leave the territories. Israel returned the Sinai to Egypt and left the Gaza Strip, but kept several settlements scattered across the West Bank, East Jerusalem and the Golan Heights. The settlements have only grown since then, and as of 2012 had a population exceeding 350,000, as noted by the Guardian.
The First Intifada, a Palestinian uprising against Israel's occupation, began at the end of 1987. Before 1991, Israel had a policy of "General Exit" permits for Palestinians who wanted to travel between Palestinian territories and Israel, as pointed out by Al Jazeera. During the Intifada, that policy was canceled, forcing Palestinians to apply for a personal permit every time they wanted to travel, even just to go from the Gaza Strip to the West Bank.
Signed in 1993, the Oslo Accords were similar to the Camp David Accords as they called for removal of Israeli forces almost completely from Gaza and from sections of the West Bank. The Oslo Accords were a series of agreements that created the Palestinian Authority, which would establish the West Bank and Gaza as one geopolitical unit. Essentially, the Oslo Accords required that Israel recognize the Palestine Liberation Organization as a legitimate negotiating partner and that, in turn, the Palestinian Authority recognize the state of Israel.
Negotiations between Israel and the Palestinian Authority continued throughout the latter part of the 1990s. Israel removed its settlements in Gaza and some northern portions of the West Bank, but it did not leave the territories completely. The Oslo Accords failed at finding a two-state solution before hardliner Ariel Sharon was sworn in as prime minister of Israel in 2001. Shortly before his election, Sharon visited the Temple Mount complex in Jerusalem -- the site of Al-Aqsa mosque, the third holiest site in Islam -- where, as recalled by Al Jazeera, he said, "The Temple Mount is in our hands." Shortly after that provocative visit, the Second Intifada began, this one much bloodier than the first.
After heavy casualties on both sides, Sharon in 2005 decided Israel would leave the Gaza Strip permanently and build a wall to protect itself. Several suicide bombings associated with Hamas had already brought it "terrorist" status in many Western countries. That year, Palestinians held an election, and Mahmoud Abbas was sworn in as the head of the Palestinian Authority. A month later, Abbas and Sharon declared a truce between Israel and the PA.
While things had calmed down between Israel and the PA, another conflict was simmering inside the Palestinian territories between Hamas and Fatah. In 2006, Hamas won the Palestinian legislative elections when it ran as the Change and Reform Party. Fatah backed a move against the new government. By June 2007, Abbas had dissolved the government and dismissed Hamas. Hamas rejected the ouster and remained in control of Gaza, effectively splitting the territories. The two refused to recognize each other as legitimate governments until April 2014.
After Hamas took control of the Gaza Strip, Israel reacted. Palestinians from Gaza who were living in the West Bank were forced to go back, while leaving Gaza was restricted to "exceptional humanitarian cases." The Gaza Strip has been periodically subjected to an air, land and sea blockade by Israel and Egypt since 2007.
Today, Gaza and Israel continue to be in conflict. The blockade led Hamas to build smuggling tunnels to Egypt, and these tunnels are now targets of the ground invasion Israel launched Thursday. This is the second phase of Operation Protective Edge, Israel's offensive against increasing rocket attacks from Gaza. At least 270 Palestinians have been killed and thousands wounded in the 10 days since the offensive began with airstrike. According to the Israel Defense Forces, Hamas has launched more than a thousand rockets into Israel during that same period.
Israel Prepared To 'Significantly Widen' Ground Invasion In Gaza
Clinton Would 'Prefer' Israel Not Invade Gaza
Egypt Canada Israel
MOST READ IN World
Arctic Cold 'No Sweat' For Electric Cars In Norway
Seventh Memphis Officer Suspended, Three Firefighters Sacked, Over Fatal Beating

redpajama_set_name: RedPajamaCommonCrawl


text:
Pangburn Home
edited by Brian Pangburn
Pam2
Pangburn Home > Pam >
Pam Tribute Page 1 Pam Tribute Page 3
Pam and Brian on the BRAA entering Virginia Completing their 4,540 mile bike ride across the US The bike loaded for the San Diego 450 mile test ride
Let me back up a couple of years. One day Pam and I were riding up in a Cessna 182 together to do a 2-way jump and she leaned over to me and said "You know some day I would like to learn how to fly one of these things". (I thought to myself, cool we are going to someday get a plane!) To make sure my plan worked I told her to go ahead and take her flying lessons first, and get her license, then I would later get mine. It worked. On April 3 1998 she did her flight exam, passed, and was a private pilot. By September we had located a Cessna 172 and purchased it. We owned that same Cessna for 13 years and she logged 425 hours behind the yoke. Pam flew our little plane from here to Washington DC, to Seattle Washington, Catalina Island, Sebastian Florida (a few times), Iowa, and down into Mexico over a dozen flights. It was Pam's plane you see. I was only the navigator. Yes I did get my pilots license but when it was the two of us in the plane, Pam always flew left seat. I looked it up and found there are 497,000 pilots in the US which puts it at .15% of all people in the US are pilots. Feel sorry for Pam?
On Feb 14th, 1999 Pam made her first camera jump. This was significant because she only had 150 jumps. The so called 'normal' procedure was to do the video from the ground and having a camera person from the air was a new concept to doing canopy formation. From that point on she was there to film 99% of every jump I did.
Later that year we purchased our first investment property, an 8-plex in Toltec. Pam wanted so much to know more about how to run an efficient rental she then obtained a job at Remax in Casa Grande running their property management. Because she worked at Remax the following year she overheard someone come into the office wanting to sell two four plex's that were condemned. She called me on the phone and had me go to take a look at them and we put an offer in for them before the seller even left the building. (we bought them too)
Pam poses by the Skydive Arizona sign
On a deep sea fishing trip the group shows off their catches Pam while catching her Amberjack
On day seven of the RAGBRAI in July 2011 Brian & Pam take a couple fo self portraits to celebrate One of Pam's favorite photo opportunities which she entitled
"Frog and Toad Together" almost every day we rode during
the sunrise and if Pam had a camera she would take this shot.
Photo Left: Pam takes off on her first paraglider ride in Lima Peru. Notice the sheer cliffs she is heading off - not bad for being scared of heights...
Photo Center: Pam shows off the Amberjack she caught on our fishing tip - which happened to be the largest one.....
Brian, Pam, and Eli finish day 2 on the 2011 RAGBRAI in Caroll IA.
Pam's curiosity in the real estate eventually led her to earn her real estate license and start to sell houses. Because of the connections she had with Remax we ended up buying a few more houses. And finally in 2004 we bought another 12 plex and since our rental income was now matching what we made on our 'regular' jobs we decided to semi-retire and focus on traveling and being able to spend more time with each other. Both kids have graduated high school, my younger bother Mark had just died from bladder cancer at the age of 36, and we felt now more than ever that pursuing love, a fulfilling life, and happiness was even more important. We wanted to spend as much time deepening our relationship and spend even more time together. And did we.
From that point on we were blessed to be able to spend 99+% of our time with each other. We skydived together, we worked together on our rentals, we traveled together, and just were able to be together almost every minute of every day. The strange part, our relationship got even stronger. Over the next 7 years it felt began to feel "strange" if Pam was not with me. Feel sorry for Pam? I could not find records on how many people were able to retire when they were 40 and travel the world – but I would bet it is far less than 1% of the population. Feel sorry for Pam?
Pam & Brian taking the kids for a ride Pam with her grandson Bradon Pam with Ray Ochoa on St. Patricks day
Pam riding along with a dolphin in Russia
Pam poses with the Chinese accuracy team in China Pam swimming with dolphins in Naberezhnye Chelny (Tatarstan) Russia
We continued to skydive and in 2003 I had the opportunity to join the U.S. Parachute Team. Pam and I continued to be on our local team together but I also 'moonlighted" on the U.S. Parachute Team. Eventually Pam would become the video person for the U.S. Parachute team as well. For those of you who do not know what the U.S. Parachute team is – it is a competition team who goes and gets to compete in world parachuting championships. The best part is the U.S. team became so effective we would go on to win 4 of the last 4 world parachuting championships. Pam and I got to travel and compete in France, Croatia, Russia (twice), and the Netherlands. And bringing home the gold for the U.S. is no easy task in any event – but Pam is one of the few percentage of people in the world who can claim they received gold medals at world competitions. Feel sorry for Pam – no way! There are 32,000 active skydivers in the United States – at the 2010 world skydiving championships only 9 US skydivers were able to bring home a gold medal. That means out of the 32,000 US skydivers only .02% of them can claim they won a gold medal at the world championships. Feel sorry for Pam? No way!
Also in 2003 we began to get involved in breaking the word record attempts for the largest canopy formations. Again Pam and I were able to jump together, her with her video and me with dozens of others trying to keep our parachutes from tangling. (Pam was much too smart to be in with all of us – she stayed back there it was safe and she could keep track of where all of us were) Together Pam and I would be able to have new world records in large formations for 64, 65, 70, 81, 85, and finally 100 parachutes all linked together. And because of these formation records it allowed Pam and I to travel an extreme amount together, in fact in 2007 we left our house on January 31st and by the time we made our 100 way world record on November that year and returned to our house on December 2nd, we were only at our house 27 days that entire 10 month period. (You better be careful what you wish for!)
Pam made 2,644 jumps with only just over 70 were without me. Feel sorry for Pam? How many people can claim they jumped 2,644 times with a fear of heights. Pam can. Feel sorry for Pam? How many people can say they are in the Guinness Book of World Records? Pam can. How many people can say they shot video footage that has been aired on the Discovery Channel, National Geographic, the BBC, and many other popular foreign networks. Pam can.
Pam and Brian riding to Tucson on the Pioneer Parkway The summit team of Tai Shan in China Pam poses with some the local children in Russia 2010
Photo Right: Brian, Pam, and Bradon pose at a wedding. (you didn't know they made suits that small?)
Photo Center: On day 35 of the BRAA, and our first day pedaling into Iowa Brian & Pam find a mulberry tree to climb.
Photo Left: Also on day 35 of the bRAA and 2,300 miles we entered Iowa and Pam jumps for joy!
In 2002 my father was a 'missionary assistant' down in Bonaire and he asked Pam and myself to come and visit. Never having hard of Bonaire before we started to research what Bonaire had to offer and found out they were famous for scuba diving. We found a 'special' price to get certified so of to Hawaii we went and earned our certification in 3 days. While in Hawaii we rented a Cessna 177 and flew from island to island looking for as many hidden waterfalls as we could find. While flying between Molokai and Oahu we witnessed a bunch of hump back whales spouting so we kept circling around them to provide an audience for their show. Feel sorry for Pam?
Later that year we did go down to Bonaire and Pam found her happy place. Scuba diving was Pam's favorite and Bonaire was her favorite place. Over the years we would feed the sharks at the Great Barrier reef off of Australia, use a reef hook in Palau to watch the sharks, barracuda, and fish much larger than Pam feed. We would dive the depths of the Blue Hole in Belize, pet the green moray eels, but it was playing with the sea turtles that gave Pam the most pleasure. But we always returned to Bonaire. In December of 2008 we were blessed to have taken Pam's son Tim and his wife Shaylee down to Bonaire to do their first open water scuba dives. And as it worked out we are pretty sure her first grandson was conceived on that trip. Do the math… I'm just saying….
Page 1 of Pam Tribute Page 3 of Pam Tribute
Subpages (1): Pam3


A neat, tidy and easy to maintain 3 bedroom 2 bathroom unit. Perfect for the first homebuyer, savvy investor or someone looking to downsize. Perfect for a lock up and leave lifestyle.
Tiled living areas and carpet throughout the bedrooms.
Large garage area which can easy fit larger 4wd vehicle and a second vehicle without any issues.
Bus stop located within a 1 minute walk, which takes you straight to Cockburn train station, local shops and large supermarket just up the road, Doctors and Chemist around the corner.
Close proximity to great schools in the area, and easy access to the Kwinana Freeway.
Flexible viewing times are available please email and we can arrange a suitable time.


ok i took my ics mp5, and put jg gears in it... i got everything right accept the anti-reversal latch... does anyone know how to properly put that thing in? i have an idea how to do it but its not doing its job for some reason. Thanks!
The anti-reversal latch is the biggest pain in the world. You have to put it in before you put in the bevel gear. The bevel gear should be the last thing you put in before you close the gearbox back up.


text:
One Morning Left is a Finnish metalcore band formed by singer Mika Lahti at 2008 in Vaasa. The band enjoys popularity in Finland and has also toured Germany, Latvia, Lithuania, Finland, Iceland, Japan and Russia. They have released three full-length albums and two EPs.

Detailed information
Their first full-length album, The Bree-Teenz, was released on 24 August 2011 by Spinefarm Records. Their second album, Our Sceneration was recorded during the summer of 2012, but was released at 22 February 2013.

In the spring of 2014 drummer and clean vocalist Tomi Takamaa announced that he had left the band. Shortly after the band would announce that their new drummer is Niko Hyttinen, who has formerly played in band Snow White's Poison Bite. They also announced that the clean vocals and second guitar would be played by Leevi Luoto from band called ARF.

On December 16, 2015, the band announced that they had been dropped from Spinefarm and signed with American-based label Imminence Records for the North American and Oceania territories. They also announced the release of their third album, Metalcore Superstars, for January 22, 2016.

After the release Metalcore Superstars band toured a lot in clubs and festivals in Finland, Baltics, Europe, Iceland and Japan.

Right now One Morning Left is making their fourth full-length album.

Members

Current members
Mika Lahti - unclean vocals
Juuso Turkki - guitar & backing vocals
Leevi Luoto - guitar, clean vocals
Touko Keippilä - keyboards
Miska Sipiläinen- bass guitar & backing vocals

Former members
Tomi Takamaa - drums and clean vocals
Tuomas Teittinen - bass guitar
Roni Harju - guitar and clean vocals
Oula Maaranen - guitar
Teemu Rautiainen – bass guitar
Valtteri Numminen – drums
Veli-Matti Kananen – bass guitar, keyboards
Ari Levola - guitar
Liam McCourt - lead guitar
Niko Hyttinen – drums
Tuukka Ojansivu – guitar & backing vocals

Discography

Albums
 2011: The Bree-TeenZ
 2013: Our Sceneration
 2016: Metalcore Superstars
 2021: Hyperactive

EPs
 2008: Panda Loves Penguin
 2009: Panda Loves Penguin Vol. 2

Singles
 2011: !liaf cipE
 2014: The Star of Africa
 2015: You're Dead, Let's Disco!
 2016: Metalcore Superstars Remixed
 2020: Neon Highway
 2021: Ruby Dragon
 2021: Sinners Are Winners
 2021: Ruthless Resistance
 2021: Creatvres
 2021: Beat It
 2021: Intergalactic Casanova
 2022: Tonight

References

External links

Finnish musical groups
Metalcore musical groups
Electronicore musical groups
Post-hardcore groups
Musical groups established in 2008
Arising Empire artists

redpajama_set_name: RedPajamaWikipedia


Why do Amish object to insurance programs?
how do the amish and mennonites deal with illness and disability?
i am a 57 year old woman who has a number of chronic disabilities. this is very isolating for me as i am pretty much bed-bound. i have faith in god which helps of course, but i do not really have any family so this makes things much harder. i used to be very active and was a high school english and foreign language teacher.
as i know how hard these groups work, i wonder how they relate to people who are unable to work due to illness,plus how they relate to the disabled/ill from a spiritual point of view.
incidentally, i would be grateful to have an occasional correspondence, by email or snail mail, with a mennonite or amish woman or family sympathetic to my situation. i read that these people do not have much time to write, but as i don't have much energy to write, that might work out well! also, is there a tradition of communal praying for the sick? thank you.
I am sorry I have not responded lately you can write me anytime, Remember God loves you and so do I.
Lisa I am not Amish nor Mennonite, but I will pray for you. I suspect that a Amish woman would be willing to trade letters with you.
I'm not Amish or Mennonite either, and maybe someone else can answer as well, but from what I've learned from Amish friends & formers, they are very sympathetic & caring to ill & disabled.
If you're feeling they may look down on one's inability to contribute, I have not seen that in stories I've heard. I know of an Amish community in Maine, who allowed an outsider woman who was legally blind to join their community. They even built her a cabin & help with her needs. This isn't common, but I'm just saying that as an example of their care for ones with special needs. Spiritually, the Amish always seem to pray "God's will" for healing & most things in life. They are not exempt from having illnesses & disabilities anymore than we are it seems.
I know of a couple ladies who may be able to write if you would send your address to me vjesusnme@aol.com then I can write them in meantime to see if they have time to write, especially with winter here in OH, less outdoor chores.
May God who is rich in mercy & grace, comfort you.
obviously the bible is very supportive of the sick and jesus has a special concern for them.my priest friends and i look at illness as a way to share in jesus's suffering, but it is hard sometimes of course. the worst is to be rejected by my own family, as if i am not worthy of anything; it is like a survival of the fittest for them and the rest are left behind. i am married but my husband has severe psychological problems so frankly is more of burden than a support for me, and i give a lot of energy and time to him.
i have a rare genetic illness called ehlers-danlos syndrome, hypermobile type. it is a collagen disorder that affects me in many many ways; because of it i had to have 7 major c-spine surgeries and have a lot of metal rods etc in my neck and spine; i also have developed cataracts at a young age for them; i also had to have multiple foot surgeries due to deformities caused by this illness, and many other problems. overall, it causes enormous fatigue and pain. i take morphine every day which hardly touches the pain but also does not make me feel drugged. i do not drink or smoke.
one of several reasons why i don't drink is that i was given a contaminated blood product so developed hepatitis c as well! ironic. i have a sleep disorder so don't sleep, i have osteoporosis, i have lyme's disease, i have a lot of rheumatological problems like bursitis, tendonitis, and osteoarthritis.
my address is: lisa kuhn, dormouse house, 34 rue de medan, 78670, villennes sur seine, france. my email is lisa.dormousehouse@gmail.com.
The Lord surely has given you grace as your attitude is inspirational to say the least. I'm studying the 4 gospels and you are correct, Jesus compassion is throughout for the sick & afflicted.
I'm a 2nd generation Californian (southern, though), transplanted in Ohio now & love it as well. Will catch up in email, but thank you for sharing, because it will gain prayer your way & help someone else reading as well, God Bless you.


S64 news, reviews and local events in S64 areas including Mexborough, Swinton, Kilnhurst, and communities in S64. Are these places the most relevant to this postcode? Let us know!
Find a Local Business Get the S64 Newsletter!


text:
Millennium Park ice skating
Ice skating in Millennium Park is a cherished Chicago tradition. Each winter, the downtown park is home to two unique public ice rinks. You can glide along on the free skate rink nestled in the shadow of a Chicago icon — "The Bean" statue — and the lights of Michigan Avenue. Then take a spin along a ribbon of ice that winds past snow-capped pines and the glimmering Chicago skyline.
Check out the seasonal ice skating rinks on Chicago's Millennium Park campus.
McCormick Tribune ice rink
Come experience a Chicago winter tradition, now in its 20th season. The McCormick Tribune skating rink in Millennium Park is a bucket list spot to take a spin on the ice. You'll be surrounded with breathtaking wintery views of Chicago, including the downtown skyline, the magical lights on the city's towering Christmas tree, and all the holiday lights reflected on the shining surface of Cloud Gate aka "The Bean."
The rink is open daily from Nov. 19, 2021 through March 6, 2022, weather permitting. Admission is free, but online reservations are required. Skate rentals are available.
Maggie Daley Park Skating Ribbon
Maggie Daley Park, neighboring Millennium Park, is home to a picturesque Skating Ribbon that meanders through snow-dusted pine trees, whimsical play spaces, and around soaring climbing walls. Its path is twice the length of a lap around a traditional skating rink, which means twice the fun for skaters.
The Skating Ribbon opens for the winter season on Nov. 19, 2021 and will remain open through March 13, 2022 (weather permitting). You'll need to make a reservation in advance this year, but that means less lines and less waiting. Reservations open on Nov. 8 and limited spots will be available so make sure to book your time slot early.
Explore more to do in Millennium Park this winter.
From this article
Beaches/Gardens/Parks
Maggie Daley Park
Millennium Park
Parks & outdoors, Sports & recreation
Ice skating in Chicago
One of the best parts about winter in Chicago is the array of charming ice rinks that pop up all across the city. These seasonal…
Blog, Parks & outdoors
Where to see fall colors in and around Chicago
Chicago's many parks, gardens, and natural areas are gorgeous year round — but they're particularly stunning in the fall. That's when you can see stunning…
Published on September 7, 2021 by Kelsey O'Connor
Festivals & special events, Music, Parks & outdoors
Millennium Park Summer Music Series
Free admission • Millennium Park The Millennium Park Summer Music Series features free outdoor concerts, showcasing a broad spectrum of established and emerging musicians in…
Entertainment, Festivals & special events, Parks & outdoors
Millennium Park Summer Film Series
Free admission • Millennium Park Through the summer months, visitors and locals flock to the Jay Pritzker Pavilion at Millennium Park to kick back, picnic,…
Food & drink, Parks & outdoors
Chicago Riverwalk restaurants and bars
The Chicago Riverwalk is one of the city's most beautiful outdoor spaces, where you can find public art, boat tours, kayak rentals, and open green…
Parks & outdoors, Special events
Celebrate spring at Chicago's best flower and garden shows
Chicago is a city with a lot of nicknames, but one of the most fitting is Urbs in Horto — Latin for "city in a…
Published on February 27, 2020 by Amy Bizzarri
Museums & art, Parks & outdoors, Tours & attractions
The Bean (Cloud Gate) in Chicago
Cloud Gate, aka "The Bean", is one of Chicago's most popular sights. The monumental work of art anchors the city's downtown park, reflecting the city's…
Family, Parks & outdoors
Family-friendly outdoor adventures in Chicago
Chicago's nearly 600 parks and 26 miles of lakefront make it easy to enjoy the great outdoors in the middle of the city. Families can…
Itineraries, Parks & outdoors
2 days, 2 Chicago waterfronts
The waterfront is a center of life in Chicago. It's where discoveries are made and adventures begin. Dedicate one day each to exploring the Chicago…


text:
Impending reduction of Spanish feed-in tariffs for renewable energy
Twitter Tweet this LinkedIn Share on LinkedIn
Under the former Special Regime, renewable energy and cogeneration facilities were entitled to transfer all their net power to the grid in exchange for: (i) the feed-in tariff; or (ii) the market price plus a regulated premium. These feed-in tariffs promised by the Spanish Government were intended to encourage and support investment in renewable energy installations.
On 12 July 2013, the Spanish Council of Ministers approved a package of urgent legislative measures aimed at removing the Spanish electricity tariff deficit. On 13 July 2013, the Spanish Government approved Royal Decree-Law 9/2013 which abolished the Special Regime and replaced it with a new remuneration system.
Instead of receiving feed-in tariffs calculated by reference to the amount of energy produced, such installations will receive a "specific remuneration" calculated by reference to the installed capacity and exploitation costs of a "standard facility", not on an individual basis. The new remuneration will be further developed by new regulations, which are expected to be passed in the coming weeks. The key elements of the new draft remuneration scheme are set out below.
In addition to local remedies such as judicial reviews challenging specific regulations and administrative decisions, adversely affected foreign investors may bring arbitration claims directly against Spain under investment treaties, such as the Energy Charter Treaty.
Such treaties provide for financial compensation for qualifying investments for breaches of substantive grounds of protection, including: expropriation (art. 13 ECT); and breach of a state's obligation to provide fair and equitable treatment (art. 10 ECT). Such claims are brought under international law and are determined by independent arbitral Tribunals.
Apart from a number of local Spanish court actions challenging the relevant local regulations, Bird & Bird has significant experience representing energy investors in investment treaty arbitration claims against Spain, including:
one of the first arbitration claims under the Energy Charter Treaty as a consequence of the adoption of new regulations (Royal Decree 1565/2010 and Royal Decree-Law 14/2010) retroactively affecting existing PV plants; and
an Energy Charter Treaty claim as a consequence of the adoption of Law 15/2012 and Royal Decree-Law 2/2014.
Main features of the new remuneration scheme
The feed-in tariff system has been abolished.
Instead, relevant installations will receive the electricity market price plus "specific remuneration", until they are capable of competing in the market. Under the new "specific remuneration" scheme, renewable energy installations will be paid on the basis of their installed capacity and their exploitation costs (i.e. O&M) and not by reference to their electricity production.
The "specific remuneration" includes: (i) an element related to each unit of installed capacity enough to cover the investment costs of a "standard facility" not recovered from the sale of electricity in the market; and (ii) an operation element to cover the difference between the exploitation costs and the incomes for the participation in the electricity market of the "standard facility".
The draft remuneration scheme is intended to enable a "reasonable return" which is calculated by reference to a "standard facility". That is to say, the proposed remuneration is to be calculated as a market price differential. Such "reasonable return" will be calculated by reference to the average yield of 10-year Spanish Government Bonds on the secondary market plus a spread.
The new remuneration scheme will be backdated to 14 July 2013. Thus appropriate adjustments might be made on feed-in tariffs already received, giving rise to backdated collection rights or payment obligations for adversely affected companies.
Hermenegildo Altozano
work Call me on: +34 91 790 6000
 Email  vCard  Location
Coral Yáñez
Partner and Co-Head of Country
Garreth Wong
work Call me on: +44 (0)20 7415 6000
Want to find more news articles?
You can search by keyword, sector or practice area and then optionally filter by a location
Keywords Sector Aerospace, Defence & Security Unmanned Aircraft Systems Automotive Aviation Aircraft Finance Energy & Utilities Renewables Energy Management Energy Network and Transmission Energy Digitalisation Rail Energy Storage Nuclear Oil & Gas Mining and Minerals Financial Services Retail and Consumer Consumer law Food & Beverage Food & Beverage Regulatory Hotels & Leisure Luxury, Fashion and Retail Food & Beverage Regulatory Digest Wellness Life Sciences and Healthcare Life Sciences Healthcare Technology & Communications Communications Devices and Components Online & Digital Cloud, Software & Services Data Centres Postal Satellite and Space activities Media, Entertainment and Sport Broadcasting Publishing Tessellate Gambling Games Marketing Music Social Sport Practice Area Banking & Finance Commercial Competition & EU Law Competitive Edge Compliance & Investigations Corporate Capital Markets Mergers & Acquisitions Private Equity Venture Capital Dispute Resolution Commercial Disputes International Arbitration Franchising International Education Infrastructure Intellectual Property Brands Copyright & related rights Patents Product design Trade Secrets Transactional IP International HR Services Employment Employee Incentives and Benefits Business Immigration Outsourcing Privacy and Data Protection Data breach and Security Incidents E-Privacy Directive GDPR EU-US Privacy Shield Data Protection Officer Services GDPR Representative Services Product compliance and liability Regulatory & Public Affairs Public Projects and Procurement Real Estate Construction and engineering Regulatory and Administrative Restructuring and Insolvency Tax Business Tax Advisory Services and Transfer Pricing Indirect Tax Personal Tax Tax Disputes Tech Transactions Trade and Customs Customs EU Trade Defence Export Controls, Sanctions and Embargoes Trade Regulatory In Focus 5G Australia's Notifiable Data Breach Scheme Autonomous Driving Blockchain Brexit Glossary of terms used in relation to Brexit Cloud Computing Compliance & Investigations Cybersecurity Esports FinTech General Data Protection Regulation Geo-Blocking Guide to IP rights in the UK In focus In Your Corner International Expansion Life Sciences Medical Devices Mobile Health Navigating Hong Kong's Competition Law OTT Oxygy Reports of Trade Mark Cases for CIPA Journal Shareholders Directive II Tracker The EU Trade Mark Reform - The biggest change to trade mark law for 20 years The Internet of Things Trade Secrets The Unitary Patent and the Unified Patent Court Unmanned Aerial Systems
Location Africa Asia-Pacific Australia Belgium Central and Eastern Europe China Czech Republic Denmark Finland France Germany Hungary India Israel Italy Japan Latin America Luxembourg Middle East Netherlands Nordic Region North America Poland Russia and the CIS Singapore Slovak Republic Southeast Europe and Turkey Spain Sweden Switzerland and Austria UK United Arab Emirates United States Western Europe
Go
Looking for news over 5 years old?