123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- # Copyright (C) 2013 Internet Systems Consortium.
- #
- # Permission to use, copy, modify, and distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
- # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
- # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
- # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
- # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- import errno
- import os
- import select
- import signal
- import isc.log
- import isc.config
- from isc.server_common.logger import logger
- from isc.log_messages.server_common_messages import *
- class BIND10ServerFatal(Exception):
- """Exception raised when the server program encounters a fatal error."""
- pass
- class BIND10Server:
- """A mixin class for common BIND 10 server implementations.
- It takes care of common initialization such as setting up a module CC
- session, and running main event loop. It also handles the "shutdown"
- command for its normal behavior. If a specific server class wants to
- handle this command differently or if it does not support the command,
- it should override the _command_handler method.
- Specific modules can define module-specific class inheriting this class,
- instantiate it, and call run() with the module name.
- Methods to be implemented in the actual class:
- _config_handler: config handler method as specified in ModuleCCSession.
- must be exception free; errors should be signaled by
- the return value.
- _mod_command_handler: can be optionally defined to handle
- module-specific commands. should conform to
- command handlers as specified in ModuleCCSession.
- must be exception free; errors should be signaled
- by the return value.
- _setup_module: can be optionally defined for module-specific
- initialization. This is called after the module CC
- session has started, and can be used for registering
- interest on remote modules, etc. If it raises an
- exception, the server will be immediately stopped.
- Parameter: None, Return: None
- _shutdown_module: can be optionally defined for module-specific
- finalization. This is called right before the
- module CC session is stopped. If it raises an
- exception, the server will be immediately
- stopped.
- Parameter: None, Return: None
- """
- # Will be set to True when the server should stop and shut down.
- # Can be read via accessor method 'shutdown', mainly for testing.
- __shutdown = False
- # ModuleCCSession used in the server. Defined as 'protectd' so tests
- # can refer to it directly; others should access it via the
- # 'mod_ccsession' accessor.
- _mod_cc = None
- # Will be set in run(). Define a tentative value so other methods can
- # be tested directly.
- __module_name = ''
- # Basically constant, but allow tests to override it.
- _select_fn = select.select
- def __init__(self):
- self._read_callbacks = {}
- self._write_callbacks = {}
- self._error_callbacks = {}
- @property
- def shutdown(self):
- return self.__shutdown
- @property
- def mod_ccsession(self):
- return self._mod_cc
- def _setup_ccsession(self):
- """Create and start module CC session.
- This is essentially private, but allows tests to override it.
- """
- self._mod_cc = isc.config.ModuleCCSession(
- self._get_specfile_location(), self._config_handler,
- self._command_handler)
- self._mod_cc.start()
- def _get_specfile_location(self):
- """Return the path to the module spec file following common convetion.
- This method generates the path commonly used by most BIND 10 modules,
- determined by a well known prefix and the module name.
- A specific module can override this method if it uses a different
- path for the spec file.
- """
- # First check if it's running under an 'in-source' environment,
- # then try commonly used paths and file names. If found, use it.
- for ev in ['B10_FROM_SOURCE', 'B10_FROM_BUILD']:
- if ev in os.environ:
- specfile = os.environ[ev] + '/src/bin/' + self.__module_name +\
- '/' + self.__module_name + '.spec'
- if os.path.exists(specfile):
- return specfile
- # Otherwise, just use the installed path, whether or not it really
- # exists; leave error handling to the caller.
- specfile_path = '${datarootdir}/bind10'\
- .replace('${datarootdir}', '${prefix}/share')\
- .replace('${prefix}', '/Users/jinmei/opt')
- return specfile_path + '/' + self.__module_name + '.spec'
- def _trigger_shutdown(self):
- """Initiate a shutdown sequence.
- This method is expected to be called in various ways including
- in the middle of a signal handler, and is designed to be as simple
- as possible to minimize side effects. Actual shutdown will take
- place in a normal control flow.
- This method is defined as 'protected'. User classes can use it
- to shut down the server.
- """
- self.__shutdown = True
- def _run_internal(self):
- """Main event loop.
- This method is essentially private, but allows tests to override it.
- """
- logger.info(PYSERVER_COMMON_SERVER_STARTED, self.__module_name)
- cc_fileno = self._mod_cc.get_socket().fileno()
- while not self.__shutdown:
- try:
- read_fds = list(self._read_callbacks.keys())
- read_fds.append(cc_fileno)
- write_fds = list(self._write_callbacks.keys())
- error_fds = list(self._error_callbacks.keys())
- (reads, writes, errors) = \
- self._select_fn(read_fds, write_fds, error_fds)
- except select.error as ex:
- # ignore intterruption by signal; regard other select errors
- # fatal.
- if ex.args[0] == errno.EINTR:
- continue
- else:
- raise
- for fileno in reads:
- if fileno in self._read_callbacks:
- for callback in self._read_callbacks[fileno]:
- callback()
- for fileno in writes:
- if fileno in self._write_callbacks:
- for callback in self._write_callbacks[fileno]:
- callback()
- for fileno in errors:
- if fileno in self._error_callbacks:
- for callback in self._error_callbacks[fileno]:
- callback()
- if cc_fileno in reads:
- # this shouldn't raise an exception (if it does, we'll
- # propagate it)
- self._mod_cc.check_command(True)
- self._shutdown_module()
- self._mod_cc.send_stopping()
- def _command_handler(self, cmd, args):
- logger.debug(logger.DBGLVL_TRACE_BASIC, PYSERVER_COMMON_COMMAND,
- self.__module_name, cmd)
- if cmd == 'shutdown':
- self._trigger_shutdown()
- answer = isc.config.create_answer(0)
- else:
- answer = self._mod_command_handler(cmd, args)
- return answer
- def _mod_command_handler(self, cmd, args):
- """The default implementation of the module specific command handler"""
- return isc.config.create_answer(1, "Unknown command: " + str(cmd))
- def _setup_module(self):
- """The default implementation of the module specific initialization"""
- pass
- def _shutdown_module(self):
- """The default implementation of the module specific finalization"""
- pass
- def watch_fileno(self, fileno, rcallback=None, wcallback=None, \
- xcallback=None):
- """Register the fileno for the internal select() call.
- *callback's are callable objects which would be called when
- read, write, error events occur on the specified fileno.
- """
- if rcallback is not None:
- if fileno in self._read_callbacks:
- self._read_callbacks[fileno].append(rcallback)
- else:
- self._read_callbacks[fileno] = [rcallback]
- if wcallback is not None:
- if fileno in self._write_callbacks:
- self._write_callbacks[fileno].append(wcallback)
- else:
- self._write_callbacks[fileno] = [wcallback]
- if xcallback is not None:
- if fileno in self._error_callbacks:
- self._error_callbacks[fileno].append(xcallback)
- else:
- self._error_callbacks[fileno] = [xcallback]
- def run(self, module_name):
- """Start the server and let it run until it's told to stop.
- Usually this must be the first method of this class that is called
- from its user.
- Parameter:
- module_name (str): the Python module name for the actual server
- implementation. Often identical to the directory name in which
- the implementation files are placed.
- Returns: values expected to be used as program's exit code.
- 0: server has run and finished successfully.
- 1: some error happens
- """
- try:
- self.__module_name = module_name
- shutdown_sighandler = \
- lambda signal, frame: self._trigger_shutdown()
- signal.signal(signal.SIGTERM, shutdown_sighandler)
- signal.signal(signal.SIGINT, shutdown_sighandler)
- self._setup_ccsession()
- self._setup_module()
- self._run_internal()
- logger.info(PYSERVER_COMMON_SERVER_STOPPED, self.__module_name)
- return 0
- except BIND10ServerFatal as ex:
- logger.error(PYSERVER_COMMON_SERVER_FATAL, self.__module_name,
- ex)
- except Exception as ex:
- logger.error(PYSERVER_COMMON_UNCAUGHT_EXCEPTION, type(ex).__name__,
- ex)
- return 1
|