Esempio n. 1
0
    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
Esempio n. 2
0
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))