示例#1
0
class Application(object):

    eventChannel = outlet('EventService')
    componentRegistry = outlet('ComponentRegistry')
    configService = outlet('ConfigService')
    txn = outlet('TransactionService')
    
    # TODO: Debug why binding a controller breaks the whole system
    # ... well I know why the it's because the SessionRegistery (which loads)
    # the controllers needs the Application object so it knows what extensions
    # to look for. Binding a controller then forces the SesionRegistery to not be loaded
    #Scheduler = outlet('Scheduler')

    log = outlet('LogService')




    MISCONFIGURED = 0  # Config file can not be interpreted
    LAUNCHING     = 1  # App spawned but has not checked in yet
    STARTING      = 2  # App has begun to load itself
    STARTED       = 3  # App is fully loaded and can service requests
    STOPPING      = 4  # App has been asked to shutdown, no new request will be accepted
    STOPPED       = 5  # App successfully shutdown, it can be relaunched if desired
    CRASHED       = 6  # App crashed during startup, or unexpectantly died, additional details may be available
    UNATTACHED    = 7  # The app appears to be running but we're not controlling it
    ATTACHING     = 8  # App was found unattached, and it was notified that we want to cntroll it.
                       # waiting for it to reestablish connection to controller
    NOTRESPONDING = 9  # App was sent a sig HUP but didn't check back in the prorper time


    CONFIG_UNMODIFIED = 0
    CONFIG_MODIFIED   = 1
    CONFIG_MISSING    = 2

    

    __slots__ = ['name', 'config','appDir', 'pidPath', 'configFile', 'context',
                 'status', 'configError', 'digest',
                 'deferred', '_dh','scheduler',
                 'appBundle','mainRunLoop',
                 ]



    ## Instance Methods ##

    def __init__(self, appDir,  authoritativeOptions = None, **defaultComps):
        self.appBundle = Bundle(appDir)
        self.configFile = self.appBundle.pathForResource('app.conf')
        self.status = Application.STOPPED

        self.digest = ""
        self.name = os.path.basename(os.path.abspath(appDir))

        if not authoritativeOptions:
            authoritativeOptions = {}
        authoritativeOptions['application.name'] =  self.name
        authoritativeOptions['application.path'] =  os.path.abspath(appDir)
        authoritativeOptions['system.hostname'] =  socket.gethostname()

        self.loadConfig()

        logService = defaultComps.get("LogService")

        if logService is None:
            # everybody needs logging, especally  during devel. If one
            # isn't passed to us, set one up to go directly to syslog

            logService = LogService()
            logService.addData('app', self.name)

            logService.setFormat('[%(app)s:%(name)s:%(levelname)s] %(message)s')
            logService.useSyslogHandler()
            defaultComps["LogService"] = logService

        # load the module that adapts our logging to somthething that twisted deferred's and failuers likes
        defaultComps["StdioOnnaStick"] =  StdioOnnaStick()

        defaultComps["ErrorFactory"] = ErrorFactory()

        

        # these two are sessions so we put their classe rather than
        # instances in componentRegistry
        defaultComps["RunLoop"] = RunLoop
        defaultComps["PortFactory"] = Port

        # our os signal handling, has to be as light weight as
        # possible. so any signal this app receives will be processed
        # by the sameRunLoop that started this app (hopefully you
        # don't have it bogged down running long synchronous tasks)

        self.mainRunLoop = RunLoop.currentRunLoop()

        self.componentRegistry = CompBinder()
        for compName, component in defaultComps.items():
            self.componentRegistry.addComponent(compName, component)

        serviceRegistry = ServiceRegistry()
        # blehq, serviceRegistry needs the application name in order to
        # load services that have been configured by convention 
        serviceRegistry.app_name = self.name
        serviceRegistry.app = self
        self.componentRegistry.addComponent("ServiceRegistry", serviceRegistry)
        # need to bind ServiceRegistry's before we can add the App as
        # a service.
        self.componentRegistry.bind()
        

        # Set up the config service with authorative options
        configService = ciConfigService()
        for option, value in authoritativeOptions.items():
          configService.set_default(option, value)

        try: # todo, remove this crap afte we pull the last bits of corba/omniorb from Rambler
            from epo import ciIApplication, ciIConfigService
            # Register the application service so that we can be remotely managed
            serviceRegistry.addService("Application", self, ciIApplication, [])
            serviceRegistry.addService("ConfigService", configService, ciIConfigService, [])
        except ImportError:
            # corba isn't installed
            serviceRegistry.addService("Application", self, None, [])
            # Note: ConfigService is placed into the componentRegistry
            # via the service registry. Not sure why I did this.
            serviceRegistry.addService("ConfigService", configService, None, [])



        self.componentRegistry.bind()


    @property
    def run_loop(self):
      return self.mainRunLoop

    def observe_value_for(self, key_path, of_object, change, *args):
      args[0](of_object)
      of_object.remove_observer(self, key_path)

    @property
    def queue(self):
      if not hasattr(self, 'scheduler'):
        self.scheduler = self.lookup('Scheduler')
      return self.scheduler.queue

    def quit(self, *args):
      # Stop the runloop while giving anything scheduled to run in the current
      # loop a chance to execute first.
      self.run_loop.waitBeforeCalling(0, self.run_loop.stop)

    def quit_with_error(self, failure):
      # Called to handle a defered failure
      self.quit()
      return failure

    def quit_with_result(self, result):
      self.quit()
      return result
          
    def wait_for(self, operation, timeout=5):
      """Test methods can use this method to execute an operation via the runLoop
      and wait for it to complete.

      Parameters:
        operation | defered: Operation to be scheduled on the run loop
        [callback]: Method to invoke after operation has finished. Note if 
                    callback is used you must explicitly call self.run_loop.stop()
                    in your unit test.
        [timeout]: Max time in seconds to wait before giving up on the operation.
      
      Discussion:
      Operations are queued in the default scheduler and then the run loop is started.
      It is an error to use this method if the RunLoop is arleady active.


      """
      
      # TODO: Remove this check when the Scheduler no longer useses deferred's
      if isinstance(operation, Deferred):
        operation.addCallbacks(self.quit_with_result, self.quit_with_error)
        self.wait(timeout)

        if hasattr(operation.result, 'raiseException'):
          operation.result.raiseException()
      else:
        operation.add_observer(self, 'is_finished', 0, self.quit)
        self.queue.add_operation(operation)
      
        self.wait(timeout)
        
      # Both deferred or an operation will return here
      return operation.result


    def wait(self, timeout=5):
      timer = self.mainRunLoop.waitBeforeCalling(timeout, self.quit)
      self.mainRunLoop.run(reset_on_stop=False)
      if not timer.called:
        timer.cancel()

    def loadConfig(self):

        configStatus = self.getConfigStatus()
        # Check to see if our config file has changed
        if configStatus == Application.CONFIG_UNMODIFIED and self.configError:
            # Config file hasn't changed and we're still misconfigured
            raise AppMisconfigured(self.configError)
        elif configStatus == Application.CONFIG_MISSING:
            self.configError = 'Missing config file %s' % self.configFile
            raise AppMisconfigured(self.configError)
        elif configStatus == Application.CONFIG_UNMODIFIED:
            return
        
        assert configStatus == Application.CONFIG_MODIFIED
                
        self.configError = ""
        
        extensions = set()
        ext_dir = self.appBundle.pathForResource('extensions')
        if os.path.isdir(ext_dir):
          extensions.update(os.listdir(ext_dir))
        
        
        self.config = Config(extensions)
    

    def getConfigStatus(self):
        md5sum = md5()

        try:
            configFile = open(self.configFile)
        except IOError:
            return Application.CONFIG_MISSING
        
        for line in configFile:
            md5sum.update(line)
        configFile.close()

        digest = md5sum.digest()
        if self.digest == "":
            self.digest = digest
            return Application.CONFIG_MODIFIED
        
        elif self.digest == digest:
            return Application.CONFIG_UNMODIFIED
        else:
            self.digest = digest
            return Application.CONFIG_MODIFIED


     
        
    def load(self):
        
        """Sets up the message bus if we have a controller then set
        our status to starting.

        onStatusSent will be called once the controller acknowledges
        our start. 
        """

        if self.getStatus() not in (Application.MISCONFIGURED, Application.STOPPED, Application.CRASHED):
            raise AppErrLoaded()
            
        old=signal.signal(signal.SIGINT, self.sighandler )
        old=signal.signal(signal.SIGTERM, self.sighandler )
        #signal.signal(signal.SIGCHLD, self.onSigChild)
        signal.signal(signal.SIGHUP, self.onSigHUP)
        # todo: it wolud be nice if we restored theses signals when
        # the App is done.


        try:
            self.switch_user()
            self.switch_group()

            # Here's were we'd load up the config file, the core services,
            # any extensions and register this object as a service, fire
            # off the Initilization event to tell the components that
            # registration is complete and finally signal our controlling
            # app (if any) that we've open for business.

            compReg = self.componentRegistry

            # this add's the ability to read services from a descriptor file
            compReg.addComponent("ServiceHandler", ServiceHandler())
            compReg.addComponent("Component", Component)


            # these add the ability to read sessions and register them
            # globaly from descriptors.
            
            compReg.addComponent("SessionRegistry", SessionRegistry())

            # Bind the core service to each other
            compReg.bind()
            self.eventChannel.registerEvent("Initializing", self, str)
            self.eventChannel.registerEvent("Shutdown", self, str)



            options = {}
            for extension in self.config.extensions:
                options[extension.name] = extension.options

            self.configService.addConfigSource(DictConfigSource(options))

            compReg.bind()

            try:
                # we need a transaction service before continuing, right
                # now the real txnservice is in our corba extension,
                # so.. if the corba bridge wasn't loaded we need to use an
                # in memory one

                compReg.lookup("TransactionService")
            except KeyError:
                from Rambler.LocalTXNService import LocalTXNService
                compReg.addComponent("TransactionService", LocalTXNService())
                compReg.bind()

            if len(self.componentRegistry.needsBinding):
                raise RuntimeError, "Server could not start because\n" +\
                      self.componentRegistry.analyzeFailures()

            self.txn.set_timeout(0)
            self.txn.begin()
            
            self.eventChannel.publishEvent("Initializing", self, self.txn.get_transaction_name())
            self.txn.commit(0)
            self.txn.set_timeout(60)
                
            self.setStatus(Application.STARTED)


        except AppMisconfigured:
            # We don't want to report this as a crash, or do we?...
            raise
        except Exception:
            
            # We got an error while loading, notify our controller
            # with it then reraise the exception to kill this
            # application
            
            msg = "".join(traceback.format_exception(*sys.exc_info()))
            self.setStatus(Application.CRASHED, msg)
            raise

    def noOp(self):
        # needed to make corba clients happy
        pass
        
    def isRunning(self):
        return self.status == Application.STARTED

    def lookup(self, component):
        return self.componentRegistry.lookup(component)


    def getStatus(self):
        return self.status

    # TODO: I think setStatus is defunc
    def setStatus(self, status, details=None):
        """Sends a life cycle notification to the controlling process, if any."""
        self.onStatusSent(None, status)


    def switch_user(self):

        if self.config.user: # The config file has a user, so attempt to switch to it
            user = self.config.user
            if os.getuid() != 0:
                 raise RuntimeError("Can't switch to user '%s' specified in %s "
                                   "because this application wasn't started as root." % 
                                   (user, self.configFile))
                
            try:
                uid = pwd.getpwnam(user)[2] # The UID.
            except KeyError:
                raise RuntimeError("Can't switch to user '%s' specified in %s "
                                   "because the user does not exist." % 
                                   (user, self.configFile))
            os.setuid(uid)

    def switch_group(self):
        if self.config.group: # The config file has a group, attempt to switch to it
            group = self.config.group
            if os.getuid() != 0:
                 raise RuntimeError("Can't switch to group '%s' specified in %s "
                                   "because this application wasn't started as root." % 
                                   (group, self.configFile))
                
            try:
                gid = gwd.getgrgid(group)[2] 
            except KeyError:
                raise RuntimeError("Can't switch to group '%s' specified in %s "
                                   "because the group does not exist." % 
                                   (group, self.configFile))
            os.setgid(gid)


    def shutdown(self):
        runLoop = self.mainRunLoop
        if self.status ==  Application.STARTED:

            self.log.info("Shutdown requested")

            # the LCP will notify us in onStatusSent when the
            # controller receives STOPPING, at which point will start
            # the shutdown sequence
            
            runLoop.callFromThread(self.setStatus, Application.STOPPING)
        elif self.status in (Application.STOPPING, Application.STOPPED):
            self.log.warn("Application received shutdown request even though it's either stopped or stopping.")
        else:

            # hmm looks like we've been asked to shutdown when we're
            # in a state where we can't do that, yet, try it again in
            # runLoops next pass. This could happpen if we're started
            # just so we can be shutdown.
            
            self.log.info("Retrying shutdown request app status "
                          "is %s instead of STARTED(%s)" %
                          (self.status, Application.STARTED) )
            
            runLoop.callFromThread(self.shutdown)


    # ASYNC event listeners

    def sighandler(self, signum, frame):
        # important! signal handlers can be called at any time
        # inbetween any python instruction. we have to take great
        # pains to ensure that no locks are aquired by a signal
        # handler or we'll dead lock. That's why we wrap this shutdown
        # call in an extra callFromThread even though self.shutdown()
        # does the same thing. Reason for this is I believe the python
        # logging module uses locks, so we don't want to try acquiring
        # one of those while this is running. If someone knows whether
        # logging is signal safe, we could relax this and just call
        # self.shutdown()

        self.mainRunLoop.callFromThread(self.shutdown)

    def onSigChild(self, signum, frame):
        self.mainRunLoop.callFromThread(self.reapChildren)

    def reapChildren(self):
        while(1):
            # keep calling waitpid, until it throws error 10, which
            # means their are no more dead children to reap.
            try:
                os.waitpid(0,0)
            except OSError, e:
                if e.errno == 10:
                    break
                else:
                    raise
示例#2
0
    def __init__(self, appDir,  authoritativeOptions = None, **defaultComps):
        self.appBundle = Bundle(appDir)
        self.configFile = self.appBundle.pathForResource('app.conf')
        self.status = Application.STOPPED

        self.digest = ""
        self.name = os.path.basename(os.path.abspath(appDir))

        if not authoritativeOptions:
            authoritativeOptions = {}
        authoritativeOptions['application.name'] =  self.name
        authoritativeOptions['application.path'] =  os.path.abspath(appDir)
        authoritativeOptions['system.hostname'] =  socket.gethostname()

        self.loadConfig()

        logService = defaultComps.get("LogService")

        if logService is None:
            # everybody needs logging, especally  during devel. If one
            # isn't passed to us, set one up to go directly to syslog

            logService = LogService()
            logService.addData('app', self.name)

            logService.setFormat('[%(app)s:%(name)s:%(levelname)s] %(message)s')
            logService.useSyslogHandler()
            defaultComps["LogService"] = logService

        # load the module that adapts our logging to somthething that twisted deferred's and failuers likes
        defaultComps["StdioOnnaStick"] =  StdioOnnaStick()

        defaultComps["ErrorFactory"] = ErrorFactory()

        

        # these two are sessions so we put their classe rather than
        # instances in componentRegistry
        defaultComps["RunLoop"] = RunLoop
        defaultComps["PortFactory"] = Port

        # our os signal handling, has to be as light weight as
        # possible. so any signal this app receives will be processed
        # by the sameRunLoop that started this app (hopefully you
        # don't have it bogged down running long synchronous tasks)

        self.mainRunLoop = RunLoop.currentRunLoop()

        self.componentRegistry = CompBinder()
        for compName, component in defaultComps.items():
            self.componentRegistry.addComponent(compName, component)

        serviceRegistry = ServiceRegistry()
        # blehq, serviceRegistry needs the application name in order to
        # load services that have been configured by convention 
        serviceRegistry.app_name = self.name
        serviceRegistry.app = self
        self.componentRegistry.addComponent("ServiceRegistry", serviceRegistry)
        # need to bind ServiceRegistry's before we can add the App as
        # a service.
        self.componentRegistry.bind()
        

        # Set up the config service with authorative options
        configService = ciConfigService()
        for option, value in authoritativeOptions.items():
          configService.set_default(option, value)

        try: # todo, remove this crap afte we pull the last bits of corba/omniorb from Rambler
            from epo import ciIApplication, ciIConfigService
            # Register the application service so that we can be remotely managed
            serviceRegistry.addService("Application", self, ciIApplication, [])
            serviceRegistry.addService("ConfigService", configService, ciIConfigService, [])
        except ImportError:
            # corba isn't installed
            serviceRegistry.addService("Application", self, None, [])
            # Note: ConfigService is placed into the componentRegistry
            # via the service registry. Not sure why I did this.
            serviceRegistry.addService("ConfigService", configService, None, [])



        self.componentRegistry.bind()