home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 2004-04-20 | 69.7 KB | 1,509 lines
########################################################################################## ## /////////////////////////////////////////////////////////////////////////////////////// ## // ## // service.py ## // ## // Authors: Eggert J≤n Magn·sson ## // Created: November 2001 ## // Project: EVE Server ## // ## // Description: ## // ## // This file contains the implementation of the base for all services in the EVE ## // Server. All services inherit from this base clasee and over-ride some functions ## // ## // Dependencies: ## // ## // blue but only for the creation of log channels ## // ## // (c) CCP 2001 ## // ## // import types import blue import stackless import log import uthread import weakref import sys import cPickle import binascii import copy import traceback def FixString(s): return str(s).replace('%','%%') #return string.replace(str(s), '%', '%%') # Service state SERVICE_STOPPED = 1 # The service is not running. SERVICE_START_PENDING = 2 # The service is starting. SERVICE_STOP_PENDING = 3 # The service is stopping. SERVICE_RUNNING = 4 # The service is running. SERVICE_CONTINUE_PENDING =5 # The service continue is pending. SERVICE_PAUSE_PENDING = 6 # The service pause is pending. SERVICE_PAUSED = 7 # The service is paused. # Service type SERVICETYPE_NORMAL = 1 # Default SERVICETYPE_BUILTIN = 2 # Means that the service wants to go into __builtin__ SERVICETYPE_EXPORT_CONSTANTS = 4 # Indicates that this service only exports primitive constants # Service control codes SERVICE_CONTROL_STOP = 1 # Requests the service to stop. SERVICE_CONTROL_PAUSE = 2 # Requests the service to pause. SERVICE_CONTROL_CONTINUE = 3 # Requests the paused service to resume. SERVICE_CONTROL_INTERROGATE = 4 # Requests the service to update immediately its current status information to the service control manager. SERVICE_CONTROL_SHUTDOWN = 5 # Requests the service to perform cleanup tasks, because the system is shutting down. SERVICE_CHECK_NONE = 0 # Indicates that this service doesn't care about security SERVICE_CHECK_CALL = 1 # Indicates that this service prefers that a security check is made every call SERVICE_CHECK_INIT = 2 # Indicates that this service prefers that a security check is made when a connection is made SERVICE_WANT_SESSIONS = 1 # Indicates that this service wants sessions ROLE_LOGIN = 0x00000001 ROLE_PLAYER = 0x00000002 ROLE_NPC = 0x00000004 ROLE_GML = 0x00000008 ## Game Master Low ROLE_GMH = 0x00000010 ## Game Master High ROLE_ADMIN = 0x00000020 ## Administrator ROLE_SERVICE = 0x00000040 ROLE_HTTP = 0x00000080 ## controls whether the session can access the server through http protocol ROLE_PETITIONEE = 0x00000100 ## role for volunteers, enables a user to claim and resolve petitions ROLE_GDL = 0x00000200 ## role for game design activity, modify messages, etc. ROLE_GDH = 0x00000400 ## role for game design activity, modify type attributes, constant values etc. ROLE_CENTURION = 0x00000800 ## role for senior volunteers, enables a user to claim and resolve petitions, escalate to GM and do bans ROLE_WORLDMOD = 0x00001000 ## role for world modelling activity ROLE_QA = 0x00002000 ## role for quality assurance ROLE_EBS = 0x00004000 ## role for eve backend system ROLE_CPUKILLER = 0x00008000 ## role for ESP pages not allowed in LONDON ROLE_PROGRAMMER = 0x00010000 ## role for executing Python code in the context of the server ROLE_REMOTESERVICE = 0x00020000 ## Services ROLE_LEGIONEER = 0x00040000 ## role for junior volunteers ROLE_CHTOPERATOR = 0x00080000 ## Operator of a given chat channel ROLE_CHTMODERATOR = 0x00100000 ## Moderator of a given chat channel ROLE_CHTCREATOR = 0x00200000 ## Creator of a given chat channel ROLE_HEALSELF = 0x00400000 ## /heal on yourself ROLE_HEALOTHERS = 0x00800000 ## /heal on others ROLE_NEWSREPORTER = 0x01000000 ## Forum and in-game news reporting role. ROLE_NEWSMODERATOR = 0x02000000 ## Forum and in-game news moderator role. ROLE_NEWSADDRUMORS = 0x04000000 ROLE_GMSUPER = 0x08000000 ## Game Master Super ROLE_N00BIE = 0x10000000 ## Newbie dude ROLE_ACCOUNTMANAGEMENT = 0x20000000 ## May manage user accounts. ROLE_ANY = 0xFFFFFFFF ROLEMASK_ELEVATEDPLAYER = (ROLE_ANY&~(ROLE_LOGIN|ROLE_PLAYER|ROLE_N00BIE)) PRE_NONE = 0 PRE_AUTH = 1 PRE_HASCHAR = 2 PRE_HASSHIP = 4 PRE_INSTATION = 8 PRE_INFLIGHT = 16 exports = {} for i in locals().items(): # print i if type(i[1]) == type(0): exports ["service." + i[0] ] = i[1] # --------------------------------------------------------------------------------------- # used by PreCall_Lock for global and class level locks. globalAndClassLocks = {} # --------------------------------------------------------------------------------------- # used to track ongoing calls callWrapperID = 0 allCallWrappers = weakref.WeakValueDictionary({}) def GetCallWrappers(): return allCallWrappers exports["service.GetCallWrappers"] = GetCallWrappers # --------------------------------------------------------------------------------------- class CachedResult: def __init__(self, result): self.result = result #---------------------------------------------------------------------------------------- class FastCallWrapper: ''' A call wrapper that does the bare minimum required to keep things rolling, session masquerading and call timing. ''' # ----------------------------------------------------------------------------------- def __init__(self,session,object,method,parent): self.__session__ = session self.__method__ = method self.__callable__ = object self.__parent__ = parent # ----------------------------------------------------------------------------------- def __call__(self,*args,**keywords): mask = self.__session__.Masquerade( { 'base.caller': weakref.ref(self.__parent__)} ) try: return apply(getattr(self.__callable__,self.__method__), args, keywords) finally: mask.UnMask() self.__dict__.clear() exports["service.FastCallWrapper"] = FastCallWrapper # --------------------------------------------------------------------------------------- class CallWrapper: ''' A rather cool call wrapper with loads of default functionality and extension capabilities. The __precall__ and __postcall__ class or instance attributes of the wrapper dictates extra call handling functionality. The call order is from left to right, both in pre and postcall. Each entry in the precall/postcall lists can be a: 1. string, naming the method to call 2. lambda, function or method, identifying the function to call 3. dict, containing both the function name (1 or 2 above) under the key 'function', and any extra info you wish to send into the call handler's extra keyword params. ''' if boot.role=='client': __precall__ = [ "PreCall_IsExportedCall", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall" ] # "PreCall_StartCallTimer", else: __precall__ = [ "PreCall_IsExportedCall", "PreCall_RoleCheck", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall", "PreCall_Lock" ] # "PreCall_StartCallTimer", __postcall__ = [ "PostCall_CachedMethodCall", "PostCall_LogCompletedMethodCall", "PostCall_UnLock"] # "PostCall_StopCallTimer" # ----------------------------------------------------------------------------------- def __init__(self,session,object,method,parent,logname=None): ''' Performs the call wrapper initialization. session is the calling session. object is the actual object being called method is the method being called parent is the parent to the call wrap (a service or object connection, probably). logname is the name of object, in loggable form ''' if logname is None: try: if hasattr(object, "__guid__"): logname = object.__guid__ s = logname.split('.') if len(s)>1: logname = s[1] else: logname = object.__class__.__name__ except: logname = "CrappyClass" self.__logname__ = logname self.__session__ = session self.__method__ = method if method.endswith("_Ex"): self.__method_without_Ex__ = method[:-3] else: self.__method_without_Ex__ = method self.__callable__ = object self.__parent__ = parent self.__metadata__ = getattr(self.__callable__,"__exportedcalls__",{}).get(self.__method_without_Ex__,[]) self.__thread__ = stackless.getcurrent() global allCallWrappers global callWrapperID callWrapperID += 1 allCallWrappers[callWrapperID] = self # ----------------------------------------------------------------------------------- def __call__(self,*args,**keywords): ''' Performs the actual method call, including all automagical stuff in the pre- and post-call list. ''' from base import CallTimer from objectCaching import CacheOK t = CallTimer(self.__logname__ + "::" + self.__method_without_Ex__) try: self.__arguments__ = args mask = self.__session__.Masquerade( { 'base.caller': weakref.ref(self.__parent__)} ) try: cookies = {} result = None exctype = None exc = None tb = None try: try: for method in self.__precall__: if type(self.__metadata__)==types.DictType: extra = self.__metadata__.get("callhandlerargs",{}) else: extra = {} cookieName = method if type(method)==types.StringType: method = getattr(self.__callable__,method,getattr(self,method,None)) cookies[cookieName] = apply(method, (self.__method__, args, keywords,), extra) except CacheOK, e: result = e except CachedResult, e: result = e except Exception, e: exctype, exc, tb = sys.exc_info() result = e except: StackTrace() exctype, exc, tb = sys.exc_info() result = RuntimeError("SomethingHorribleHappenedDuringPreCall") try: if result is None: args2 = args if self.__method_without_Ex__==self.__method__: preargs = [] if type(self.__metadata__)==types.DictType: if "preargs" in self.__metadata__: preargs = self.__metadata__["preargs"] elif len(self.__metadata__)>1: preargs = self.__metadata__[1:] if preargs: args2 = (len(preargs) * [None]) + list(args) for i in range(len(preargs)): args2[i] = getattr(session,preargs[i]) # Don't bother with a default value. Preargs already checked. result = apply(getattr(self.__callable__,self.__method_without_Ex__), args2, keywords) except UserError, e: exctype, exc, tb = sys.exc_info() result = e except Exception, e: exctype, exc, tb = sys.exc_info() result = e except: StackTrace(text = "something horrible happened during call") exctype, exc, tb = sys.exc_info() result = RuntimeError("SomethingHorribleHappenedDuringCall") for method in self.__postcall__: try: if type(self.__metadata__)==types.DictType: extra = self.__metadata__.get("callhandlerargs",{}) else: extra = {} if type(method)==types.StringType: method = getattr(self.__callable__,method,getattr(self,method,None)) apply(method, (cookies, result, self.__method__, args, keywords,), extra) except CacheOK, e: result = e except CachedResult, e: result = e except: StackTrace() if result is not None: if isinstance(result, Exception): raise result, None, tb elif isinstance(result,CachedResult): return result.result finally: # IMPORTANT: If you don't nullify the output of sys.exc_info(), you leak a lot. exctype = None exc = None tb = None finally: mask.UnMask() self.__dict__.clear() return result finally: t.Done() # ----------------------------------------------------------------------------------- def PreCall_StartCallTimer(self, method, args, keywords, **mykeywords): ''' Starts a call timer for this call ''' from base import CallTimer return CallTimer(self.__logname__ + "::" + self.__method_without_Ex__) # ----------------------------------------------------------------------------------- def PostCall_StopCallTimer(self, cookies, result, method, args, keywords, **mykeywords): ''' Stops a call timer for this call ''' sct = cookies.get("PreCall_StartCallTimer",None) if sct is not None: sct.Done() # ----------------------------------------------------------------------------------- def PreCall_IsExportedCall(self, method, args, keywords, **mykeywords): ''' Verifies that the call being made is available to the general public, or subsets thereof. ''' # can use method instead of method without ex, since services don't care whether or # not a method is exported. if (method not in self.__callable__.__exportedcalls__) and (method not in ("MachoResolve","MachoBindObject","MachoResolveObject",)): if not ((self.__method__ != self.__method_without_Ex__) and (self.__method_without_Ex__ in self.__callable__.__exportedcalls__)): raise RuntimeError("In %s, method %s is not exported" % (self.__logname__, self.__method_without_Ex__)) # ----------------------------------------------------------------------------------- def PreCall_RoleCheck(self, method, args, keywords, **mykeywords): ''' Verifies that the calling session is allowed to call this method with regards to role restrictions. ''' if not (session.role&ROLE_SERVICE): if type(self.__metadata__)==types.DictType: role = self.__metadata__.get("role",ROLE_SERVICE) elif len(self.__metadata__): role = self.__metadata__[0] elif method in ("MachoResolve","MachoBindObject","MachoResolveObject",): role = ROLE_ANY else: role = ROLE_SERVICE if not (role&session.role): session.LogSessionError("Called %s::%s, which requires role 0x%x, which the user doesn't have"%(self.__logname__,method, role)) raise RuntimeError("RoleNotAssigned", "%s::%s requires role 0x%x, which the user doesn't have. Calling session: %s"%(self.__logname__,method, role, str(session))) # ----------------------------------------------------------------------------------- def PreCall_CheckPreArgs(self, method, args, keywords, **mykeywords): ''' Checks that all the specified preargs are indeed available and have legal values. ''' noImplicits = (session.role&ROLE_SERVICE and method.endswith('_Ex')) if type(self.__metadata__)==types.DictType: preargs = self.__metadata__.get("preargs",[]) elif len(self.__metadata__): preargs = self.__metadata__[1:] else: preargs = [] for each in preargs: if noImplicits and each not in ["sid", "userid"]: continue prearg = getattr(session,each) if prearg is None: session.LogSessionError("A required parameter exception occurred while the user was calling %s::%s. The missing parameter was %s"%(self.__logname__,self.__method_without_Ex__,each)) raise RuntimeError("RequiredParameterException", "%s::%s requires parameter %s, which the session doesn't have. Calling session: %s"%(self.__logname__,self.__method_without_Ex__, each, str(session))) # ----------------------------------------------------------------------------------- def PostCall_LogCompletedMethodCall(self, cookies, result, method, args, keywords, **mykeywords): ''' Logs a method call that has been completed, successfully or not ''' logChannel = getattr(self.__callable__,"logChannel",log.methodcalls) if logChannel.IsOpen(128): if isinstance(result,Exception): eorr = ", EXCEPTION=" else: eorr = ", retval=" if keywords: logwhat = [ self.__logname__,"::",method," args=",args,", keywords={",keywords,"}",eorr,result ] else: logwhat = [ self.__logname__,"::",method," args=",args,eorr,result ] t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.__logname__ + "::LogCompletedMethodCall") try: try: logChannel.Log(''.join(map(str, logwhat)), 128, 1) except TypeError: logChannel.Log( '[X]'.join(map(str, logwhat)).replace('\0','\\0'), 128, 1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) # ----------------------------------------------------------------------------------- def PreCall_Lock(self, method, args, keywords, **mykeywords): ''' Acquires a call lock for this group The PreCall_Lock function takes extra keywords to determine the desired behaviour. key values description ======= ======================= ============================================= lock.lock true or false Whether or not this call uses a call lock. If true, then the rest of this section applies, if false, then this stuff is ignored. This is useful if f.ex. you place a lock on a class by default, and wish to disable it for a few calls. Defaults to true. lock.scope connection, session, The scope of the lock. If 'connection' then instance, service, the lock only applies to all calls coming class, server, up this same object connection. If 'session', usersession then it applies to all calls coming up from the same session. If 'instance', then it applies to all calls on this particular object instance. If 'service', then it applies to all calls made to the service or any bound object it has, without regard to class or whatnot. If 'class', then it applies to all calls made to this object class (as determined by the logname), and if 'server' then it applies to all calls on this server, without regard to object class, service, etc. 'usersession' is same as 'session', but only user sessions trigger the lock. Defaults to usersession. lock.useargs true or false If this is set true, then takes the arguments to the call into account in the locking scope. In other words, 'instance'+'useargs'=1 would mean all calls on this instance with these args to this method call. Note that if you specify useargs, you have to have args that are usable as in a dict key. Defaults to false. lock.group <string> A unique string that identifies the lock within the given scope. Note that the same string may be used f.ex. both in instance and server scopes without effecting each other. Defaults to the method name. lock.reaction block, inform, raise, If 'block', then the call will block on a ignore semaphore waiting for a chance to continue. If 'inform', then the method will be called with added information in the keywords param of the method, so that the callee can determine that this has occurred, and react to it. If 'raise', then a default UserError will be raised, telling the user to sod off. If 'ignore' then just returns None automagically. Defaults to block lock.raisemsg The message key to raise. %(othermethod)s, %(thismethod)s, %(group)s, and %(scope)s are available as extra stuff. Defaults to "CallGroupLocked", which is a rather horrible system message. ''' lock = mykeywords.get('lock.lock',1) if lock: scope = mykeywords.get('lock.scope','usersession') if (scope=='usersession') and (self.__session__.role&ROLE_SERVICE): return useargs = mykeywords.get('lock.useargs',0) group = mykeywords.get('lock.group',method) reaction = mykeywords.get('lock.reaction','block') if useargs: k = ("PreCall_Lock",group,args,) else: k = ("PreCall_Lock",group,) if scope in ('session','usersession'): k = (k, "session", self.__session__.sid) l = self.__session__.GetSessionVariable( k, [uthread.Semaphore( k ), None] ) elif scope=='connection': k = (k, "connection", self.__parent__.__c2ooid__) l = self.__parent__.GetConnectionVariable( k ) if l is None: l = [uthread.Semaphore( k ), None] self.__parent__.SetConnectionVariable( k, l ) elif scope=='instance': k = (k, "instance", self.__parent__.GetInstanceID()) l = self.__parent__.GetInstanceVariable( k ) if l is None: l = [uthread.Semaphore( k ), None] self.__parent__.SetInstanceVariable( k, l ) elif scope=='service': # if we're calling a service, then the behaviour is the same as for 'instance' # but if we're calling an object, it's not. if isinstance(self.__callable__,Service): k = (k, "service", self.__parent__.GetInstanceID()) l = self.__parent__.GetInstanceVariable( k ) if l is None: l = [uthread.Semaphore( k ), None] self.__parent__.SetInstanceVariable( k, l ) else: srv = sm.StartService(self.__parent__.__serviceName__) k = (k, "service", srv.startedWhen) l = session.GetInstanceVariable(srv, k) if l is None: l = [uthread.Semaphore( k ), None] session.SetInstanceVariable( srv, k, l ) elif scope in ('class','global',): if scope=='class': try: k = (k, scope, self.__callable__.__class__.__name__) except: k = (k, scope, "logname", self.__logname__) l = globalAndClassLocks.get(k, None) if l is None: l = [uthread.Semaphore( k ), None] globalAndClassLocks[k] = l if l[0].count==0: if reaction=='raise': othermethod = '<unknown>' if l[1] is not None: othermethod = l[1][0] raise UserError(mykeywords.get("lock.raisemsg","CallGroupLocked"),{"thismethod":method, "group":group, "scope":scope, "othermethod":othermethod }) elif reaction=='inform': keywords["reentrancy"] = l[1] return None elif reaction=='ignore': raise CachedResult(None) l[0].acquire() l[1] = (method, args, keywords,) return l # ----------------------------------------------------------------------------------- def PostCall_UnLock(self, cookies, result, method, args, keywords, **mykeywords): l = cookies.get("PreCall_Lock",None) if l is not None: l[1] = None l[0].release() # ----------------------------------------------------------------------------------- def PreCall_CachedMethodCall(self, method, args, keywords, **mykeywords): ''' Performs the pre-call method call caching check, and strips out the machoVersion flag, if present. ''' machoVersion = keywords.get("machoVersion",None) if machoVersion: if len(keywords)==1: keywords.clear() else: del keywords["machoVersion"] #return None cacheInfo = sm.StartService("objectCaching").PerformCachedMethodCall(self.__callable__, self.__logname__, self.__method_without_Ex__, args, machoVersion) cachable, versionCheck, throwOK, cachedResultRecord, cacheKey, cacheDetails = cacheInfo if cachable and (not versionCheck): if throwOK: from objectCaching import CacheOK raise CacheOK() elif cachedResultRecord: if machoVersion: raise CachedResult(cachedResultRecord["rret"]) else: raise CachedResult(cachedResultRecord["lret"]) return (cacheInfo, machoVersion,) # ----------------------------------------------------------------------------------- def PostCall_CachedMethodCall(self, cookies, result, method, args, keywords, **mykeywords): ''' Performs the post-call method call caching logic, resulting in the call result being cached if appropriate. ''' #return None cookie = cookies.get("PreCall_CachedMethodCall",None) if cookie is not None: if not isinstance(result,Exception): cacheInfo, machoVersion = cookie if cacheInfo[0]: # cachable srv = sm.StartService("objectCaching") from objectCaching import CachedMethodCallResult, CacheOK from util import CachedObject cachable, versionCheck, throwOK, cachedResultRecord, cacheKey, cacheDetails = cacheInfo cmcr = CachedMethodCallResult(cacheKey, cacheDetails, result,) srv.CacheMethodCall(self.__logname__,self.__method_without_Ex__,args,cmcr) if (machoVersion is not None): if (machoVersion!=1) and (cmcr.version[1]==machoVersion[1]): srv.LogInfo("ObjectCaching ",self.__logname__,"::",self.__method_without_Ex__,"(",args,") is the correct version. raising CacheOK") raise CacheOK() else: srv.LogInfo("ObjectCaching ",self.__logname__,"::",self.__method_without_Ex__,"(",args,") is not the correct version. returning a cachable result. caller=",machoVersion,", server=",cmcr.version) raise CachedResult(cmcr) else: srv.LogInfo("ObjectCaching ",self.__logname__,"::",self.__method_without_Ex__,"(",args,") the client didn't provide a macho version") exports["service.CallWrapper"] = CallWrapper # ----------------------------------------------------------------------------------- class ServiceCallWrapper(CallWrapper): ''' A call wrapper with the needs of services in mind. This call wrapper add's waiting for the service to run to it's precall checks. ''' if boot.role=='client': __precall__ = [ "PreCall_WaitForRunningState", "PreCall_IsExportedCall", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall" ] #"PreCall_StartCallTimer", else: __precall__ = [ "PreCall_WaitForRunningState", "PreCall_IsExportedCall", "PreCall_RoleCheck", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall" ] # "PreCall_StartCallTimer", # ----------------------------------------------------------------------------------- def PreCall_WaitForRunningState(self, method, args, keywords, **mykeywords): ''' Wait until the service achieves running state, or stops. Doesn't actually try to start the service. ''' i = 0 while self.__callable__.state != SERVICE_RUNNING: if self.__callable__.state == SERVICE_STOPPED: raise RuntimeError("ServiceStopped", self.__callable__) blue.pyos.synchro.Sleep(100) if ((i % 600)==0) and (i>0): self.__callable__.LogWarn("PreCallHandler: ",method, args, keywords, " has been sleeping for a long time waiting for ",self.__logname__," to either get to running state, or to stopped state") exports["service.ServiceCallWrapper"] = ServiceCallWrapper # ----------------------------------------------------------------------------------- """ Using vanilla call wrappers for the moment for object calls. class ObjectCallWrapper(CallWrapper): ''' A call wrapper with the needs of bound objects in mind. This call wrapper does a magical rebind check, giving the callee an opportunity to bounce the caller off to another node or the like. ''' if boot.role=='client': __precall__ = [ "PreCall_StartCallTimer", "PreCall_ReResolveObject", "PreCall_UpdateMoniker", "PreCall_IsExportedCall", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall" ] else: __precall__ = [ "PreCall_StartCallTimer", "PreCall_ReResolveObject", "PreCall_UpdateMoniker", "PreCall_IsExportedCall", "PreCall_RoleCheck", "PreCall_CheckPreArgs", "PreCall_CachedMethodCall" ] # ----------------------------------------------------------------------------------- def PreCall_ReResolveObject(self, method, args, keywords, **mykeywords): ''' Double-checks that the bound object still resides on this node, and gives MachoResolveObject an opportunity to raise an UpdateMoniker or otherwise tell the caller to bugger off. ''' if (self.__parent__.__serviceName__ is not None) and (self.__parent__.__bindParams__ is not None) and (self.__parent__.__redirectObject__ is None): nodeID = sm.StartService(self.__parent__.__serviceName__).MachoResolveObject(self.__parent__.__bindParams__,self.__GetJustQuery()) if nodeID != sm.StartService("machoNet").GetNodeID(): self.__parent__.__redirectObject__ = (self.__parent__.__serviceName__,self.__parent__.__bindParams__,) # ----------------------------------------------------------------------------------- def PreCall_UpdateMoniker(self, method, args, keywords, **mykeywords): ''' Handles redirecting the call if we were previously directed to do so. ''' if self.__parent__.__redirectObject__ is not None: nodeID = sm.StartService(self.__parent__.__serviceName__).MachoResolveObject(self.__parent__.__redirectObject__[1],self.__GetJustQuery()) from util import UpdateMoniker raise UpdateMoniker(self.__parent__.__redirectObject__[0], self.__parent__.__redirectObject__[1], nodeID) # ----------------------------------------------------------------------------------- def __GetJustQuery(self): if stackless.getcurrent().block_trap or stackless.getcurrent().is_main: return 3 else: return 0 exports["service.ObjectCallWrapper"] = ObjectCallWrapper """ exports["service.ObjectCallWrapper"] = CallWrapper ######################################################################################### globalMiniTraceStats = {} class MiniTraceCallWrapper: def __init__(self, service, func, funcName, depth): self.service = weakref.proxy(service) self.func = func self.funcName = funcName self.depth = depth def __call__(self, *args, **kw): if self.service.logChannel.IsOpen(1): stats = globalMiniTraceStats[(self.service.__guid__,self.funcName)] f = stackless.getcurrent().frame k0 = None depth = 0 while f and ((self.depth==-1 and k0 is None) or (depth<self.depth)): depth += 1 f = f.f_back if f: k = f.f_code.co_filename, f.f_code.co_name, traceback.f_lineno(f) if (k0 is None) and (k[1]!="__call__"): k0 = k stats[k] = stats.get(k,0) + 1 self.service.LogInfo(self.funcName,args,kw," is being called by ",k0[0]," in line ",k0[2]," of ",k0[0]) return apply( self.func, args, kw) else: return apply( self.func, args, kw) exports["service.MiniTraceStats"] = globalMiniTraceStats ######################################################################################### ## ////////////////////////////////////////////////////////////////////////////////////// ## // ## // Class Service - Service ## // ## // The base for all service in the eve cluster ## // ## // class Service: __dependencies__ = [] __required__ = ["Sessions"] __sessionparams__ = [] __servicetype__ = SERVICETYPE_NORMAL __name__ = "service" __displayname__ = "Basic Service" __persistvars__ = [] __nonpersistvars__ = [] __exportedcalls__ = {} __notifyevents__ = [] __configvalues__ = {} __counters__ = {} __machocacheobjects__ = 1 __machoresolve__ = None # None, clustersingleton, station, solarsystem, or location # ----------------------------------------------------------------------------------- # Service - Constructor # ----------------------------------------------------------------------------------- def __init__(self): self.boundObjects = {} self.serviceLocks = {} self.startedWhen = blue.os.GetTime(1) ## Initialize the log channel for the service guidParts = self.__guid__.split('.') self.__logname__ = guidParts[1] self.logChannel = blue.pyos.CreateLogChannel(guidParts[0], guidParts[1]) self.InitService() if len(self.__counters__) and not ("counter" in self.__dependencies__): self.__dependencies__.append("counter") self.logContexts = {} for each in ('Info','Warn', 'Error', 'Perf','Fatal','Counter',): ##self.logContexts[each] = self.__logname__ + "::Log" + each self.logContexts[each] = "Logging::" + each # ----------------------------------------------------------------------------------- def MachoResolve(self, sess): ''' By default, all services run on all machines, except clustersingletons, station and solarsystem based services. ''' if self.__machoresolve__ is not None: mn = sm.services["machoNet"] if self.__machoresolve__ == "clustersingleton": return mn.GetNodeFromAddress(self.__logname__,0) if not (sess.role&ROLE_SERVICE): # Remote service calls are performed on the correct machine as a prerequisite. if self.__machoresolve__ == "station": if not sess.stationid: return "You must be located at a station to use this service" return mn.GetNodeFromAddress("station",sess.stationid) elif self.__machoresolve__ == "solarsystem": if not sess.solarsystemid: return "You must be located in a solar system to use this service" return mn.GetNodeFromAddress("beyonce",sess.solarsystemid) elif self.__machoresolve__ in ("location","locationPreferred"): if not sess.locationid: if self.__machoresolve__=="locationPreferred": return None return "You must be located in a solar system or at station to use this service" elif sess.solarsystemid: return mn.GetNodeFromAddress("beyonce",sess.solarsystemid) else: return mn.GetNodeFromAddress("station",sess.locationid) else: raise RuntimeError("This service is crap (%s)"%self.__logname__) return None # ----------------------------------------------------------------------------------- def LockService(self,lockID): ''' For those operations which support and/or require service locks, here's the locking function for ya. ''' if not lockID in self.serviceLocks: lock = uthread.CriticalSection(("service::serviceLock",self.__logname__,lockID,)) self.serviceLocks[lockID] = lock else: lock = self.serviceLocks[lockID] lock.acquire() return lock # ----------------------------------------------------------------------------------- def UnLockService(self,lockID,lock): ''' For those operations which support and/or require service locks, here's the unlocking function for ya. ''' if self.serviceLocks.get(lockID,None) != lock: if lock is not None: self.LogError("Service lock locked up. This is the lock in servicelocks=", str(self.serviceLocks.get(lockID,None)), ", and this is the lock we got returned=",str(lock)) lock.release() else: self.LogError("Service lock locked up big time. This is the lock in servicelocks=", str(self.serviceLocks.get(lockID,None)), ", and this is the lock we got returned=",str(lock)) else: self.serviceLocks[lockID].release() if lockID in self.serviceLocks and self.serviceLocks[lockID].IsCool(): del self.serviceLocks[lockID] # ----------------------------------------------------------------------------------- def StartMiniTrace(self,funcName, depth = 16): ''' Wraps this service's function 'funcName' in a mini-trace call wrapper, that helps find who the farg is calling the function so often... ''' if funcName not in globalMiniTraceStats: globalMiniTraceStats[(self.__guid__,funcName)] = {} setattr(self, funcName, MiniTraceCallWrapper(self,getattr(self,funcName),funcName, depth)) # ----------------------------------------------------------------------------------- def StopMiniTrace(self,funcName, depth = 16): ''' Wraps this service's function 'funcName' in a mini-trace call wrapper, that helps find who the farg is calling the function so often... ''' if funcName not in globalMiniTraceStats: del globalMiniTraceStats[(self.__guid__,funcName)] setattr(self, funcName, getattr(self,funcName).func) # ----------------------------------------------------------------------------------- def GetDependants(self): dependants = [] guidParts = self.__guid__.split('.') if len(guidParts): myName = guidParts[1] else: myName = None for i in sm.services: if myName in sm.services[i].__dependencies__: guidParts = sm.services[i].__guid__.split('.') if len(guidParts): dependants.append(guidParts[1]) else: dependants.append(sm.services[i].__guid__) return dependants # ----------------------------------------------------------------------------------- def GetDeepDependants(self,theArr): newbies = [] for each in self.GetDependants(): if not (each in theArr): newbies.append( each ) theArr.append ( each ) for each in newbies: sm.services[each].GetDeepDependants(theArr) # ----------------------------------------------------------------------------------- def GetDeepDependencies(self,theArr): newbies = [] for each in self.__dependencies__: if not (each in theArr): newbies.append( each ) theArr.append ( each ) for each in newbies: sm.services[each].GetDeepDependencies(theArr) # ----------------------------------------------------------------------------------- # InitService # ----------------------------------------------------------------------------------- def InitService(self): self.state = SERVICE_STOPPED # __getattr__ operator - Providing config values as well # ----------------------------------------------------------------------------------- def __getattr__(self, key): if key in self.__dict__: return self.__dict__[key] if key in self.__configvalues__: daKey="%s.%s"%(self.__logname__,key,) return prefs.GetValue(daKey, boot.GetValue(daKey,self.__configvalues__[key])) elif key in self.__counters__: self.__dict__[key] = self.counter.CreateCounter(key,self.__counters__[key]) return self.__dict__[key] elif hasattr(self.__class__,key): return getattr(self.__class__,key) else: raise AttributeError, key # __setattr__ operator - Modifying config values as well # ----------------------------------------------------------------------------------- def __setattr__(self, key, value): if key in self.__configvalues__: prefs.SetValue("%s.%s"%(self.__logname__,key),value) return self.__dict__[key]=value # ----------------------------------------------------------------------------------- # ControlMessage # ----------------------------------------------------------------------------------- def ControlMessage(self, control): if control == SERVICE_CONTROL_STOP: self.state = SERVICE_STOPPED elif control == SERVICE_CONTROL_PAUSE: self.state = SERVICE_PAUSED elif control == SERVICE_CONTROL_CONTINUE: self.state = SERVICE_RUNNING elif control == SERVICE_CONTROL_INTERROGATE: pass elif control == SERVICE_CONTROL_SHUTDOWN: self.state = SERVICE_STOPPED # ----------------------------------------------------------------------------------- def DudLogger(self, *args, **keywords): pass # ----------------------------------------------------------------------------------- # LogMethodCall # ----------------------------------------------------------------------------------- def LogMethodCall(self, *args, **keywords): if self.logChannel.IsOpen(128): t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Info"]) try: try: if (len(args)==1): s = str(args[0]) else: s = ' '.join(map(str, args)) self.logChannel.Log(s, 128, 1) except TypeError: self.logChannel.Log( '[X]'.join(map(str, args)).replace('\0','\\0'), 128, 1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) else: self.LogMethodCall = self.DudLogger # ----------------------------------------------------------------------------------- # LogInfo # ----------------------------------------------------------------------------------- def LogInfo(self, *args, **keywords): if self.logChannel.IsOpen(1): t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Info"]) try: try: if (len(args)==1): s = str(args[0]) else: s = ' '.join(map(str, args)) for i in range(0,1012,253): # four lines max. if i==0: if len(s)<=255: x = s else: x = s[:(i+253)] else: x = " - " + s[i:(i+253)] self.logChannel.Log(x, 1, 1) if (i+253)>=len(s): break except TypeError: self.logChannel.Log( '[X]'.join(map(str, args)).replace('\0','\\0'), 1, 1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) else: self.LogInfo = self.DudLogger # ----------------------------------------------------------------------------------- # LogWarn # ----------------------------------------------------------------------------------- def LogWarn(self, *args, **keywords): if self.logChannel.IsOpen(2) or (charsession and (not boot.role=='client')): t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Warn"]) try: try: if (len(args)==1): s = str(args[0]) else: s = ' '.join(map(str, args)) for i in range(0,2530,253): # ten lines max. if i==0: if len(s)<=255: x = s else: x = s[:(i+253)] else: x = " - " + s[i:(i+253)] if self.logChannel.IsOpen(2): self.logChannel.Log(x, 2, 1) if (charsession and (not boot.role=='client')): charsession.LogSessionHistory(x,None,1) if (i+253)>=len(s): break except TypeError: x = '[X]'.join(map(str, args)).replace('\0','\\0') if self.logChannel.IsOpen(2): self.logChannel.Log( x, 2, 1) if (charsession and (not boot.role=='client')): charsession.LogSessionHistory(x,None,1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) elif boot.role=='client': self.LogWarn = self.DudLogger # ----------------------------------------------------------------------------------- # LogError # ----------------------------------------------------------------------------------- def LogError(self, *args, **keywords): if self.logChannel.IsOpen(4) or charsession: t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Error"]) try: try: if (len(args)==1): s = str(args[0]) else: s = ' '.join(map(str, args)) for i in range(0,10120,253): # forty lines max. if i==0: if len(s)<=255: x = s else: x = s[:(i+253)] else: x = " - " + s[i:(i+253)] if self.logChannel.IsOpen(4): self.logChannel.Log(x, 4, 1) if charsession: charsession.LogSessionHistory(x,None,1) if (i+253)>=len(s): break except TypeError: x = '[X]'.join(map(str, args)).replace('\0','\\0') if self.logChannel.IsOpen(4): self.logChannel.Log( x, 4, 1) if charsession: charsession.LogSessionHistory(x,None,1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) #else: # self.LogError = self.DudLogger # ----------------------------------------------------------------------------------- # LogFatal # ----------------------------------------------------------------------------------- def LogFatal(self, *args, **keywords): if self.logChannel.IsOpen(8) or charsession: t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Fatal"]) try: try: if (len(args)==1): s = str(args[0]) else: s = ' '.join(map(str, args)) for i in range(0,10120,253): # forty lines max. if i==0: if len(s)<=255: x = s else: x = s[:(i+253)] else: x = " - " + s[i:(i+253)] if self.logChannel.IsOpen(8): self.logChannel.Log(x, 8, 1) if charsession: charsession.LogSessionHistory(x,None,1) if (i+253)>=len(s): break except TypeError: x = '[X]'.join(map(str, args)).replace('\0','\\0') if self.logChannel.IsOpen(8): self.logChannel.Log( x, 8, 1) if charsession: charsession.LogSessionHistory(x,None,1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) #else: # self.LogFatal = self.DudLogger # ----------------------------------------------------------------------------------- # LogPerf # ----------------------------------------------------------------------------------- def LogPerf(self, *args, **keywords): if self.logChannel.IsOpen(32): t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Perf"]) try: try: if (len(args)==1): self.logChannel.Log( str(args[0]), 32, 1) else: self.logChannel.Log( ' '.join(map(str, args)), 32, 1) except TypeError: self.logChannel.Log( '[X]'.join(map(str, args)).replace('\0','\\0'), 32, 1) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) else: self.LogPerf = self.DudLogger # ----------------------------------------------------------------------------------- # LogCounter # ----------------------------------------------------------------------------------- def LogCounter(self, value): if self.logChannel.IsOpen(64): t = stackless.getcurrent() if hasattr(t,"localStorage"): timer = t.PushTimer(self.logContexts["Counter"]) try: self.logChannel.LogCounter(value) finally: if hasattr(t,"localStorage"): t.PopTimer(timer) else: self.LogCounter = self.DudLogger # ----------------------------------------------------------------------------------- # Run # ----------------------------------------------------------------------------------- def Run(self, memStream = None): Import("blue") self.LogInfo("Service: %s starting" % (self.__guid__,)) self.boundObjects = {} # ----------------------------------------------------------------------------------- # Entering # ----------------------------------------------------------------------------------- def Entering(self): pass # ----------------------------------------------------------------------------------- # Stop # ----------------------------------------------------------------------------------- def Stop(self,memStream = None): for bo in self.boundObjects.values(): for sess in bo.sessionConnections.values(): sess.DisconnectObject(bo) self.boundObjects = {} # ----------------------------------------------------------------------------------- # GetSessionState # ----------------------------------------------------------------------------------- def GetSessionState(self, session_ = None): if session_ is not None: session = session_ if session is None: raise RuntimeError("No session") return session.GetBag(self.__guid__) # ----------------------------------------------------------------------------------- # GetServiceType # ----------------------------------------------------------------------------------- def GetServiceType(self): types = { SERVICETYPE_NORMAL : "Normal", SERVICETYPE_BUILTIN : "Built-in", SERVICETYPE_EXPORT_CONSTANTS : "Constants", } return types[self.__servicetype__] # ----------------------------------------------------------------------------------- # GetServiceState # ----------------------------------------------------------------------------------- def GetServiceState(self): states = { SERVICE_STOPPED : "Stopped", SERVICE_START_PENDING : "Start Pending", SERVICE_STOP_PENDING : "Stop Pending", SERVICE_RUNNING : "Running", SERVICE_CONTINUE_PENDING : "Continue Pending", SERVICE_PAUSE_PENDING : "Pause Pending", SERVICE_PAUSED : "Paused", } return states[self.state] # ----------------------------------------------------------------------------------- # GetHtmlState # ----------------------------------------------------------------------------------- def GetHtmlState(self, writer, session = None, request = None): import types import htmlwriter if writer is None: return "(no info available)" else: writer.Write(self.HTMLDumpProperties("Basic Service properties", "wpServiceProperties", self, session, request )) if len(self.__configvalues__): hd = ["Key","Pretty Name","Value","Default Value", "Info"] li = [] for each in self.__configvalues__.iterkeys(): prettyname = each info = None value = getattr(self,each) if hasattr(self,"GetHtmlStateDetails"): r = self.GetHtmlStateDetails(each,value,0) if r: prettyname, info = r[0], r[1] if value != self.__configvalues__[each]: value = "<b>%s</b>"%value li.append( [each, prettyname, value, self.__configvalues__[each], info ] ) li.sort(lambda a, b: -(a[0].upper() < b[0].upper())) writer.Write(htmlwriter.WebPart("Service Config Values", htmlwriter.OutlineTable(hd, li), "wpServiceConfigValues")) if len(self.__counters__): hd = ["Key","Pretty Name","Type","Current Value","Description"] li = [] for each in self.__counters__.iterkeys(): cname = each prettyname = cname ctype = self.__counters__[each] cvalue = 0 if hasattr(self,each): cvalue = getattr(self,each).Value() info = None if hasattr(self,"GetHtmlStateDetails"): r = self.GetHtmlStateDetails(each,cvalue,0) if r: prettyname, info = r[0], r[1] li.append( [cname, prettyname, ctype, cvalue, info ] ) li.sort(lambda a, b: -(a[0].upper() < b[0].upper())) writer.Write(htmlwriter.WebPart("Service Counters", htmlwriter.OutlineTable(hd, li), "wpServiceCounters")) writer.WriteH2("Service Member Variables") writer.Write(self.HTMLDumpGenericMembers(self, session, request)) if hasattr(self,"boundObjects"): if request: objectDetail = request.QueryString("objectDetail") if objectDetail is not None: objectDetail = cPickle.loads( binascii.a2b_hex( objectDetail ) ) else: objectDetail = None object = self.boundObjects.get(objectDetail,None) if object: writer.WriteH2("Bound Object %s"%str(objectDetail)) if object.__doc__ is not None: writer.Write(htmlwriter.Swing(object.__doc__).replace("\n", "<br>")) writer.Write(self.HTMLDumpProperties("Properties of Bound Object %s"%str(objectDetail), "wpBoundObjectProperties %s"%str(objectDetail), object, session, request )) writer.Write(self.HTMLDumpGenericMembers(object, session, request)) def HTMLDumpGenericMembers(self, dumpWho, session, request): import htmlwriter if request: detail = request.QueryString("detail") else: detail = None def Str(v): import htmlwriter try: return htmlwriter.Swing(str(v)) except: return "(?)" li = [] theItems = dumpWho.__dict__.keys() theItems.sort(lambda a, b: -(a.upper() < b.upper())) something = 0 for k in theItems: v = dumpWho.__dict__[k] if hasattr(dumpWho,"__dependencies__") and (k in dumpWho.__dependencies__): continue if hasattr(dumpWho,"__configvalues__") and (k in dumpWho.__configvalues__): continue if hasattr(dumpWho,"__counters__") and (k in dumpWho.__counters__): continue # service.py variables: if k in ("state","logChannel",'session','boundObjects','__servicelock__','objectConnections','sessionConnections','__sessionfilter__'): continue # reported elsewhere r = None ok = k if hasattr(dumpWho,"GetHtmlStateDetails"): r = dumpWho.GetHtmlStateDetails(k,v,k==detail) if r: k,v = r[0], r[1] else: if k != detail: v = Str(v) else: import types var = getattr(dumpWho, detail, None) if type(var) in [types.ListType, types.TupleType]: lines = [] for i in var: lines.append([Str(i)]) v = htmlwriter.OutlineTable([], lines) elif type(var) == types.DictType: lines = [] for key, val in var.iteritems(): lines.append([Str(key), Str(val)]) v = htmlwriter.OutlineTable([], lines) else: v = Str(v) v = "<pre>%s</pre>" % (v) k = htmlwriter.Link("", k, [request.query, {"detail": ok}]) li.append([k, v]) something = 1 return htmlwriter.OutlineTable([], li) if not something: return "This service does not have any custom member variables" def HTMLDumpProperties(self, title, page, dumpWho, session, request): import htmlwriter def Str(v): import htmlwriter try: return htmlwriter.Swing(str(v)) except: return "(?)" hd = ["Property","Value"] li = [] deepdependencies = [] if hasattr(dumpWho,"GetDeepDependencies"): dumpWho.GetDeepDependencies(deepdependencies) deepdependants = [] if hasattr(dumpWho,"GetDeepDependendants"): dumpWho.GetDeepDependants(deepdependants) dependencies = [] if hasattr(dumpWho,"__dependencies__"): dependencies = dumpWho.__dependencies__ dependants = [] if hasattr(dumpWho,"GetDependants"): dependants = dumpWho.GetDependants() sessionparams = [] if hasattr(dumpWho,"__sessionparams__"): sessionparams = dumpWho.__sessionparams__ persistvars = [] if hasattr(dumpWho,"__persistvars__"): persistvars = dumpWho.__persistvars__ nonpersistvars = [] if hasattr(dumpWho,"__nonpersistvars__"): nonpersistvars = dumpWho.__nonpersistvars__ notifyevents = [] if hasattr(dumpWho,"__notifyevents__"): notifyevents = dumpWho.__notifyevents__ exportedcalls = [] if hasattr(dumpWho,"__exportedcalls__"): exportedcalls = dumpWho.__exportedcalls__ objectConnections = [] if hasattr(dumpWho,"objectConnections"): for each in dumpWho.objectConnections.itervalues(): objectConnections.append( Str(each) ) sessionConnections = [] if hasattr(dumpWho,"sessionConnections"): for each in dumpWho.sessionConnections.itervalues(): sessionConnections.append( Str(each) ) boundObjects = [] if hasattr(dumpWho,"boundObjects"): boundObjects = dumpWho.boundObjects.keys() sessionfilter = [] if hasattr(dumpWho,"__sessionfilter__"): sessionfilter = dumpWho.__sessionfilter__ def Alphabetize(arr): try: li = arr[:] li.sort(lambda a, b: -(a.upper() < b.upper())) return li except: return arr properties = { "Objects bound to me" : Alphabetize(boundObjects), "Sessions using me" : Alphabetize(sessionConnections), "Objects using me" : Alphabetize(objectConnections), "Session Filter" : Alphabetize(sessionfilter), "I depend on" : Alphabetize(dependencies), "I depend on - recursively" : Alphabetize(deepdependencies), "Who depends on me" : Alphabetize(dependants), "Who depends on me - recursively" : Alphabetize(deepdependants), "Session Parameters" : Alphabetize(sessionparams), "Persistant Variables" : Alphabetize(persistvars), "Non-persistant Variables" : Alphabetize(nonpersistvars), "I listen to these events" : Alphabetize(notifyevents), "I export these calls" : Alphabetize(exportedcalls), } def xyzzy(k,val): if k in ("I depend on","I depend on - recursively","Who depends on me","Who depends on me - recursively"): return htmlwriter.Link("", str(val), [request.query, {"svcname": str(val)}]) elif k=='Objects bound to me': return htmlwriter.Link("", str(val), [request.query, {"objectDetail": binascii.b2a_hex( cPickle.dumps( val, 1)) }]) else: return str(val) for k in properties: s = "" comma = "" for each in properties[k]: s += comma + xyzzy(k,each) comma = ", " if (s != ""): li.append([k, s]) if hasattr(dumpWho,"GetServiceState"): li.append(["Service State", dumpWho.GetServiceState()]) if hasattr(dumpWho,"GetServiceState"): li.append(["Service Type", dumpWho.GetServiceState()]) if hasattr(dumpWho,"session"): li.append(["Service Session", str(dumpWho.session)]) if hasattr(dumpWho,"__servicelock__"): li.append(["Service Lock", str(getattr(dumpWho,"__servicelock__",None))]) li.sort(lambda a, b: -(a[0].upper() < b[0].upper())) return htmlwriter.WebPart(title, htmlwriter.OutlineTable(hd, li), page) exports["service.Service"] = Service