#!/usr/bin/env python # # Base web service class. # #################################################################### import sys import os import cherrypy from cherrypy.process import plugins from cherrypy import server from dm.common.constants import dmStatus from dm.common.utility.configurationManager import ConfigurationManager from dm.common.utility.loggingManager import LoggingManager from dm.common.utility.dmModuleManager import DmModuleManager from dm.common.exceptions.configurationError import ConfigurationError #################################################################### class DmRestWebServiceBase: DEFAULT_N_SERVER_REQUEST_THREADS = 50 DEFAULT_SERVER_SOCKET_TIMEOUT = 30 CONFIG_SECTION_NAME = 'WebService' CONFIG_OPTION_NAME_LIST = [ 'serviceHost', 'servicePort', 'sslCertFile', 'sslKeyFile', 'sslCaCertFile', 'stationName' ] class SignalHandler: def __init__(self, signal, oldSignalHandler): self.signal = signal self.oldSignalHandler = oldSignalHandler self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__) def signalHandler(self): self.logger.debug('%s signal handler called' % self.signal) DmModuleManager.getInstance().stopModules() self.oldSignalHandler() def __init__(self, routeMapper): self.configurationManager = ConfigurationManager.getInstance() self.routeMapper = routeMapper self.options = None self.args = None self.logger = None def prepareOptions(self): from optparse import OptionParser p = OptionParser() p.add_option('-d', '--daemon', action="store_true", dest='daemonFlag', default=False, help="Run as a daemon.") p.add_option('-p', '--pid-file', dest='pidFile', default=None, help="Store process id in the given file.") p.add_option('', '--config-file', dest='configFile', default=None, help="Service configuration file.") p.add_option('-P', '--port', dest='servicePort', default=None, help="Service port.") p.add_option('-H', '--host', dest='serviceHost', default=None, help="Service host.") p.add_option('-C', '--ssl-ca-cert', dest='sslCaCertFile', default=None, help='SSL CA certificate path (used for client SSL certificate verification). Requires --ssl-key and --ssl-cert.') p.add_option('-c', '--ssl-cert', dest='sslCertFile', default=None, help='SSL certificate path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.') p.add_option('-k', '--ssl-key', dest='sslKeyFile', default=None, help='SSL key path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.') p.add_option('', '--n-server-threads', dest='nServerThreads', default=DmRestWebServiceBase.DEFAULT_N_SERVER_REQUEST_THREADS, help='Number of service request handler threads (defaut: %s).' % DmRestWebServiceBase.DEFAULT_N_SERVER_REQUEST_THREADS) return p def initDmModules(self): return None def getDefaultServerHost(self): return None def getDefaultServerPort(self): return None # Instantiate modified signal handler that stops dm modules first, # and then does the default action. def modifySignalHandlers(self, engine): pluginsSignalHandler = plugins.SignalHandler(engine) handlers = pluginsSignalHandler.handlers # Add handler for interrupt handlers['SIGINT'] = engine.exit # Modify all signal handlers for signal in handlers.keys(): self.logger.debug('Modifying signal: %s' % signal) oldSignalHandler = handlers[signal] self.logger.debug('Old signal handler: %s' % oldSignalHandler) signalHandler = DmRestWebServiceBase.SignalHandler(signal, oldSignalHandler) self.logger.debug('Setting signal handler to: %s' % signalHandler.signalHandler) handlers[signal] = signalHandler.signalHandler pluginsSignalHandler.subscribe() def initServerLog(self): cherrypyLogLevel = self.configurationManager.getCherrypyLogLevel() cherrypy.log.error_log.setLevel(cherrypyLogLevel) cherrypy.log.error_file = self.configurationManager.getCherrypyLogFile() cherrypy.log.error_log.propagate = False cherrypy.log.access_log.setLevel(cherrypyLogLevel) cherrypy.log.access_file = self.configurationManager.getCherrypyAccessFile() cherrypy.log.access_log.propagate = False def updateServerConfig(self): serviceHost = self.configurationManager.getServiceHost() servicePort = int(self.configurationManager.getServicePort()) nServerThreads = int(self.options.nServerThreads) configDict = { 'server.socket_host' : serviceHost, 'server.socket_port' : servicePort, 'server.thread_pool' : nServerThreads, 'log.screen' : (self.options.daemonFlag != True), } cherrypy.config.update(configDict) def readConfigFile(self, configFile): configFile = self.options.configFile if not configFile: configFile = self.configurationManager.getConfigFile() else: self.configurationManager.setConfigFile(configFile) if not os.path.exists(configFile): raise ConfigurationError('Configuration file %s does not exist.' % configFile) # Read file and set config options self.configurationManager.setOptionsFromConfigFile(DmRestWebServiceBase.CONFIG_SECTION_NAME, DmRestWebServiceBase.CONFIG_OPTION_NAME_LIST, configFile) def readCommandLineOptions(self): # This method should be called after reading config file # in case some options are overridden if self.options.sslCaCertFile != None: self.configurationManager.setSslCaCertFile(self.options.sslCaCertFile) if self.options.sslCertFile != None: self.configurationManager.setSslCertFile(self.options.sslCertFile) if self.options.sslKeyFile != None: self.configurationManager.setSslKeyFile(self.options.sslKeyFile) if self.options.serviceHost != None: self.configurationManager.setServiceHost(self.options.serviceHost) if self.options.servicePort != None: self.configurationManager.setServicePort(self.options.servicePort) def prepareServer(self): try: optionParser = self.prepareOptions() (self.options, self.args) = optionParser.parse_args() # Read config file and override with command line options self.readConfigFile(self.options.configFile) self.readCommandLineOptions() # Turn off console log for daemon mode. self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__) if self.options.daemonFlag: LoggingManager.getInstance().setConsoleLogLevel('CRITICAL') dispatch = self.routeMapper.setupRoutes() self.logger.debug('Using route dispatch: %s' % dispatch) config = { '/' : { 'request.dispatch' : dispatch, }, } # No root controller as we provided our own. cherrypy.tree.mount(root=None, config=config) self.initServerLog() self.updateServerConfig() self.logger.info('Using host %s' % self.configurationManager.getServiceHost()) self.logger.info('Using port %s' % self.configurationManager.getServicePort()) self.logger.debug('Using %s request handler threads' % self.options.nServerThreads) except Exception, ex: if self.logger is not None: self.logger.exception(ex) else: import traceback print '\n%s' % sys.exc_info()[1] traceback.print_exc(file=sys.stderr) sys.exit(dmStatus.DM_ERROR) # Run server. def __runServer(self): self.logger.info('Starting service') engine = cherrypy.engine # Set up Deamonization if self.options.daemonFlag: plugins.Daemonizer(engine).subscribe() self.logger.debug('Daemon mode: %s' % self.options.daemonFlag) if self.options.pidFile != None: plugins.PIDFile(engine, self.options.pidFile).subscribe() self.logger.debug('Using PID file: %s' % self.options.pidFile) sslCertFile = self.configurationManager.getSslCertFile() sslKeyFile = self.configurationManager.getSslKeyFile() sslCaCertFile = self.configurationManager.getSslCaCertFile() if sslCertFile != None and sslKeyFile != None: server.ssl_ca_certificate = None if sslCaCertFile != None: server.ssl_ca_certificate = self.options.sslCaCertFile self.logger.info('Using SSL CA cert file: %s' % sslCaCertFile) server.ssl_certificate = sslCertFile self.logger.info('Using SSL cert file: %s' % sslCertFile) server.ssl_private_key = sslKeyFile self.logger.info('Using SSL key file: %s' % sslKeyFile) server.ssl_module = 'builtin' #server.ssl_module = 'pyopenssl' # Increase timeout to prevent early SSL connection terminations server.socket_timeout = DmRestWebServiceBase.DEFAULT_SERVER_SOCKET_TIMEOUT # Setup the signal handler to stop the application while running. if hasattr(engine, 'signal_handler'): self.logger.debug('Subscribing signal handler') engine.signal_handler.subscribe() self.modifySignalHandlers(engine) # Turn off autoreloader. self.logger.debug('Turning off autoreloader') engine.autoreload.unsubscribe() # Start the engine. try: self.logger.debug('Starting engine') engine.start() # Prepare dm services. self.logger.debug('Starting modules') self.initDmModules() DmModuleManager.getInstance().startModules() except Exception, ex: self.logger.exception('Service exiting: %s' % ex) DmModuleManager.getInstance().stopModules() return dmStatus.DM_ERROR self.logger.info('Service ready') engine.block() DmModuleManager.getInstance().stopModules() self.logger.info('Service done') return dmStatus.DM_OK # Run server instance. def run(self): self.prepareServer() sys.exit(self.__runServer()) #################################################################### # Testing if __name__ == '__main__': pass