#!/usr/bin/env python # # Base web service class. # #################################################################### import sys import cherrypy from cherrypy.process import plugins from cherrypy import server from dm.common.constants import dmStatus from dm.common.utility import configurationManager from dm.common.utility import loggingManager from dm.common.utility import dmModuleManager #################################################################### class DmRestWebService: DEFAULT_N_SERVER_REQUEST_THREADS = 10 DEFAULT_SERVER_SOCKET_TIMEOUT = 30 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 = loggingManager.getInstance().getLogger(self.__class__.__name__) self._logger.info('Initializing service') 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', '--pidfile', dest='pidfile', default=None, help="Store the process id in the given file.") p.add_option('-P', '--port', dest='serverPort', default=self.getDefaultServerPort(), help="Server port.") p.add_option('-H', '--host', dest='serverHost', default=self.getDefaultServerHost(), help="Server host.") p.add_option('-C', '--ssl-ca-cert', dest='sslCaCert', 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='sslCert', 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='sslKey', 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=DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS, help='Number of service request handler threads (defaut: %s).' % DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS) return p def initDmModules(self): pass 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 = DmRestWebService.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): configDict = { 'server.socket_host' : self._options.serverHost, 'server.socket_port' : int(self._options.serverPort), 'server.thread_pool' : int(self._options.nServerThreads), 'log.screen' : (self._options.daemonFlag != True), } cherrypy.config.update(configDict) def prepareServer(self): self._logger.debug('Preparing service configuration') try: optionParser = self.prepareOptions() (self._options, self._args) = optionParser.parse_args() 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._options.serverHost) self._logger.info('Using port %s' % self._options.serverPort) self._logger.debug('Using %s request handler threads' % self._options.nServerThreads) except Exception, ex: self._logger.exception(ex) 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) if self._options.sslCert != None and self._options.sslKey != None: server.ssl_ca_certificate = None if self._options.sslCaCert != None: server.ssl_ca_certificate = self._options.sslCaCert self._configurationManager.setSslCaCertFile(self._options.sslCaCert) self._logger.info('Using SSL CA cert file: %s' % self._options.sslCaCert) server.ssl_certificate = self._options.sslCert self._configurationManager.setSslCertFile(self._options.sslCert) self._logger.info('Using SSL cert file: %s' % self._options.sslCert) server.ssl_private_key = self._options.sslKey self._configurationManager.setSslKeyFile(self._options.sslKey) self._logger.info('Using SSL key file: %s' % self._options.sslKey) server.ssl_module = 'builtin' #server.ssl_module = 'pyopenssl' # Increase timeout to prevent early SSL connection terminations server.socket_timeout = DmRestWebService.DEFAULT_SERVER_SOCKET_TIMEOUT # Setup the signal handler to stop the application while running. if hasattr(engine, 'signal_handler'): engine.signal_handler.subscribe() if hasattr(engine, 'console_control_handler'): engine.console_control_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. # Doing this before engine starts may cause issues with existing timers. 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.prepareOptions() self.prepareServer() sys.exit(self.__runServer()) #################################################################### # Testing if __name__ == '__main__': pass