def __init__(self): if 'services' in sys.modules: e = "Instantiate ServiceManager before the services module!" raise AssertionError(e) self.SERVICE_STATE = {} self._parent = None drone.log('Loading Services') import services mask = ('loadAll', ) #mask as private #truely become service module for var, val in vars(services).items(): if var.startswith('_'): continue if var in mask: continue setattr(self, var, val) #bind the module globals to self services.loadAll() #load all services before replacing module sys.modules['services'] = self #replace the services module from droned.models.action import AdminAction #droneblaster action hooks self._action = AdminAction('service') self._action.expose('start', self._startService, ('name', ), 'starts the service') self._action.expose('stop', self._stopService, ('name', ), 'stops the service') self._action.expose('disable', self._disableService, ('name', ), 'prevent the service from starting') self._action.expose('enable', self._enableService, ('name', ), 'allow the service to start') self._action.expose('status', self._statusService, ('name', ), 'status of the service') self._action.expose('list', lambda: \ self._action.resultContext('\n'.join([ i for i in \ self.EXPORTED_SERVICES.keys() ]), None), (), 'list all services' ) self._action.buildDoc() #finalize the admin action
class AppManager(Entity): """This is a generic application container service. It's sole purpose is to provide an abstraction to the application plugin. Think of this as an application service container. """ implements(IDroneModelAppManager) serializable = True #global container lock globalLock = defer.DeferredLock() running = property(lambda s: hasattr(s, '_task') and s._task.running) model = property(lambda s: IDroneDApplication(s)) #late plugin lookup action = property(lambda s: AdminAction(s.name)) invoke = property(lambda s: s.action.invoke) resultContext = property(lambda s: s.action.resultContext) exposedMethodInfo = property(lambda s: s.action.exposedMethodInfo) exposedMethods = property(lambda s: s.action.exposedMethods) instances = property(lambda s: App(s.name).localappinstances) labels = property(lambda s: (i.label for i in s.instances)) #whether or not the application service should discover apps for us discover = property(lambda s: not all([i.running for i in s.instances])) def __init__(self, name): self.name = name #this is for user defined storage self.applicationContext = {} #allow the models to block methods from registering self.blockedMethods = set() #create a local lock self.busy = defer.DeferredLock() #track events self.events = {} def log(self, message, label=None): """route logging messages to the application log and allow for custom labeling to be applied @param message: (string) @param label: (string) or (None) @return None """ info = self.name if label: info += ',%(label)s' % locals() logWithContext(type=info, route='application')(message) def __getstate__(self): """used to serialize the application model""" return { 'name': self.name, 'applicationContext': self.applicationContext } @staticmethod def construct(state): """rebuild the model with context @param state: (dict) return AppManger(state['name']) """ manager = AppManager(state['name']) manager.applicationContext = state['applicationContext'] return manager def start(self): """This is used by service binding to start""" if self.running: raise AssertionError('already running') #not only is this a safety, but makes sure the model is bound #donot ever remove this, otherwise first run won't automatically #create appinstances or any other models. #should be self, but make sure we avoid a race if self.model.service != AppManager(self.name): raise InvalidPlugin('Plugin for %s is invalid' % (self.name, )) self.action.log = self.log #override default logging #create default exposed methods, the model can override any of these self.expose('add', self.addInstance, ('instance', ), "Configure the specified instance", BUSYLOCK=True) self.expose('remove', self.removeInstance, ('instance', ), "Unconfigure the specified instance", BUSYLOCK=True) self.expose('start', self.startInstance, ('instance', ), "Start the instance", BUSYLOCK=True, INSTANCED=True) self.expose('stop', self.stopInstance, ('instance', ), "Stop the instance", BUSYLOCK=True, INSTANCED=True) self.expose('status', self.statusInstance, ('instance', ), "Status the instance", INSTANCED=True) self.expose('enable', self.enableInstance, ('instance', ), "Enable the instance", INSTANCED=True) self.expose('disable', self.disableInstance, ('instance', ), "Disable the instance", INSTANCED=True) self.expose('debug', self.debug, ('bool', ), "Turn application container debugging on or off") self.expose( 'labels', lambda: self.resultContext('\n'.join(sorted(self.labels)), None, ** {'labels': sorted(self.labels)}), (), "lists all application instance labels") #build our documentation self.rebuildHelpDoc() #check conditional events self._task = LoopingCall(self.conditionalEvents) self._task.start(1.0) def conditionalEvents(self): """check the status of conditional events""" if self.busy.locked: return #skip conditional event processing while busy for appevent in self.events.values(): if not appevent.condition: continue appevent.occurred() def registerEvent(self, name, callback, **kwargs): """Interface to Register Service Events""" #the self parameter will help ensure this event is unique to the service self.events[name] = ApplicationEvent(self.name, name, callback, **kwargs) def triggerEvent(self, name, data=None, delay=0.0): """Interface to trigger an out of band service event""" assert name in self.events, "No such event '%s'" % (name, ) return self.events[name].trigger(data, delay) def disableEvent(self, name): """Interface to disable a previously registered service event""" assert name in self.events, "No such event '%s'" % (name, ) self.events[name].event.disable() def enableEvent(self, name): """Interface to enable a previously disabled registered service event """ assert name in self.events, "No such event '%s'" % (name, ) self.events[name].event.enable() def stop(self): """This is used by service binding to stop""" try: if not self.running: raise AssertionError('not running') self._task.stop() #clear the event dictionary and delete events while self.events: name, appevent = self.events.popitem() if appevent.loop and appevent.loop.running: appevent.loop.stop() ApplicationEvent.delete(appevent) except: self.debugReport() #remove this appmanager's actions AdminAction.delete(self.action) ########################################################################### # This part of the class exposes the Model API to outside world ########################################################################### @synchronizedDeferred(globalLock) def unexpose(self, name, blacklist=True): """Removes an exposed method, probably not a good idea to expose""" #add method to blocked list if blacklist: self.blockedMethods.add(name) if name in self.exposedMethods: del self.exposedMethods[name] info = None found = False for info in self.exposedMethodInfo: (n, a, d) = info if n == name: found == True break if info and found: self.exposedMethodInfo.remove(info) def rebuildHelpDoc(self): """rebuild exposed method documentation""" self.action.buildDoc() @synchronizedDeferred(globalLock) def expose(self, name, method, args, doc, **kwargs): """Wraps the models exposed methods for gremlin and make methods available via blaster protocol for action invocation. expose(self, name, method, methodSignature, description, **kwargs) name: (string) - This is the action parameter to expose method: (callable) - This is the function name to call args: (tuple) - layout for parsing args description: (string) - Help Documentation to expose kwargs: INSTANCED: (bool) - sets the instanceOperator decorator for administrator's ease of use. BUSYLOCK: (bool) - sets the synchronizedDeferred decorator for this AppManager. GLOBALLOCK: (bool) - sets the synchronizedDeferred decorator for synchronizing all AppManagers. """ if name in self.blockedMethods: return #method was blocked by the model, probably #allow models to override the defaults and print a warning if name in self.exposedMethods: self.log('Warning method "%s" is already exposed' % (name, )) return #These decorators must be applied in a specific order of precedence requireInstance = kwargs.pop('INSTANCED', False) requireBusyLock = kwargs.pop('BUSYLOCK', False) requireGlobalLock = kwargs.pop('GLOBALLOCK', False) #applying decorators at runtime if requireBusyLock or requireGlobalLock or requireInstance: #ordering is critical if requireInstance: #this bizarre decorator is used b/c we need instance info. method = self.instanceOperation(method) if requireBusyLock: sync = synchronizedDeferred(self.busy) method = sync(method) if requireGlobalLock: sync = synchronizedDeferred(self.globalLock) method = sync(method) self.exposedMethodInfo.append((name, args, doc)) self.exposedMethods[name] = method ########################################################################### # This part of the class is for Generic actions that all apps perform ########################################################################### #FIXME is this really needed? def debug(self, var): """Enable or Disable application model debugging. You should extend this if you know how to enable application debugging in your custom 'application model'. returns deferred - already called """ #assume blaster which is string based, sent the message var = str(var) #for safety a = var.lower() context = {'code': 0} template = '[%(application)s] Debug ' try: if a == 'true': self.model.debug = True defer.setDebugging(True) template += 'Enabled' elif a == 'false': self.model.debug = False defer.setDebugging(False) template += 'Disabled' else: raise TypeError('input must be a bool, True/False') except Exception, exc: template += str(exc) context['code'] = 1 return defer.succeed(self.resultContext(template, None, **context))