Browse Source

[2854] add "bind10_server" module, a mixin providing common server behavior.

JINMEI Tatuya 12 years ago
parent
commit
c27421b7e7

+ 1 - 0
configure.ac

@@ -1395,6 +1395,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/python/isc/notify/tests/notify_out_test
            src/lib/python/isc/log/tests/log_console.py
            src/lib/python/isc/log_messages/work/__init__.py
+           src/lib/python/isc/server_common/bind10_server.py
            src/lib/dns/gen-rdatacode.py
            src/lib/python/bind10_config.py
            src/lib/cc/session_config.h.pre

+ 3 - 1
src/lib/python/isc/server_common/Makefile.am

@@ -1,12 +1,14 @@
 SUBDIRS = tests
 
 python_PYTHON = __init__.py tsig_keyring.py auth_command.py dns_tcp.py
-python_PYTHON += datasrc_clients_mgr.py
+python_PYTHON += datasrc_clients_mgr.py bind10_server.py
 python_PYTHON += logger.py
 
 pythondir = $(pyexecdir)/isc/server_common
 
 BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+BUILT_SOURCES += bind10_server.py
+
 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
 
 pylogmessagedir = $(pyexecdir)/isc/log_messages/

+ 115 - 0
src/lib/python/isc/server_common/bind10_server.py.in

@@ -0,0 +1,115 @@
+# 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 os
+import signal
+
+import isc.log
+from isc.server_common.logger import logger
+from isc.log_messages.server_common_messages import *
+
+class BIND10Server:
+    """A mixin class for common BIND 10 server implementations.
+
+    Methods to be implemented in the actual class:
+
+    """
+    # 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
+    __module_name = None
+
+    @property
+    def shutdown(self):
+        return self.__shutdown
+
+    def _setup_ccsession(self):
+        self._cc = isc.config.ModuleCCSession(self._get_specfile_location(),
+                                              self._config_handler,
+                                              self._command_handler)
+
+    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.
+
+        """
+        if 'B10_FROM_SOURCE' in os.environ:
+            specfile_path = os.environ['B10_FROM_SOURCE'] + '/src/bin/' + \
+                self.__module_name
+        else:
+            specfile_path = '@datadir@/@PACKAGE@'\
+                .replace('${datarootdir}', '@datarootdir@')\
+                .replace('${prefix}', '@prefix@')
+        return specfile_path + '/' + self.__module_name
+
+    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' so tests can call it; otherwise
+        it's private.
+
+        """
+        self.__shutdown = True
+
+    def _run_internal(self):
+        while not self.__shutdown:
+            pass
+
+    def _config_handler(self):
+        pass
+
+    def _command_handler(self):
+        pass
+
+    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._run_internal()
+            return 0
+        except Exception as ex:
+            logger.error(PYSERVER_COMMON_UNCAUGHT_EXCEPTION, type(ex).__name__,
+                         str(ex))
+
+        return 1

+ 16 - 0
src/lib/python/isc/server_common/server_common_messages.mes

@@ -21,6 +21,9 @@
 # have that at this moment. So when adding a message, make sure that
 # the name is not already used in src/lib/config/config_messages.mes
 
+% PYSERVER_COMMON_COMMAND %1 server has received '%2' command
+The server process received the shown name of command from other module.
+
 % PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
 Debug message.  A complete DNS message has been successfully
 transmitted over a TCP connection, possibly after multiple send
@@ -44,6 +47,14 @@ The destination address and the total size of the message that has
 been transmitted so far (including the 2-byte length field) are shown
 in the log message.
 
+% PYSERVER_COMMON_SERVER_STARTED %1 server has started
+The server process has successfully started and is now ready to receive
+commands and updates.
+
+% PYSERVER_COMMON_SERVER_STOPPED %1 server has stopped
+The server process has successfully stopped and is no longer listening for or
+handling commands.  Normally the process will soon exit.
+
 % PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
 A debug message noting that the global TSIG keyring is being removed from
 memory. Most programs don't do that, they just exit, which is OK.
@@ -57,3 +68,8 @@ to be loaded from configuration.
 A debug message. The TSIG keyring is being (re)loaded from configuration.
 This happens at startup or when the configuration changes. The old keyring
 is removed and new one created with all the keys.
+
+% PYSERVER_COMMON_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2
+The BIND 10 server process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.

+ 1 - 0
src/lib/python/isc/server_common/tests/Makefile.am

@@ -1,5 +1,6 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = tsig_keyring_test.py dns_tcp_test.py datasrc_clients_mgr_test.py
+PYTESTS += bind10_server_test.py
 EXTRA_DIST = $(PYTESTS)
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 92 - 0
src/lib/python/isc/server_common/tests/bind10_server_test.py

@@ -0,0 +1,92 @@
+# 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 unittest
+import os
+import signal
+
+import isc.log
+import isc.config
+from isc.server_common.bind10_server import *
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class TestException(Exception):
+    """A generic exception class specific in this test module."""
+    pass
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+    def __init__(self, specfile, config_handler, command_handler):
+        pass
+
+class MockServer(BIND10Server):
+    def _setup_ccsession(self):
+        orig_cls = isc.config.ModuleCCSession
+        isc.config.ModuleCCSession = MyCCSession
+        try:
+            super()._setup_ccsession()
+        except Exception:
+            raise
+        finally:
+            isc.config.ModuleCCSession = orig_cls
+
+class TestBIND10Server(unittest.TestCase):
+    def setUp(self):
+        self.__server = MockServer()
+
+    def test_init(self):
+        """Check initial conditions"""
+        self.assertFalse(self.__server.shutdown)
+
+    def test_trigger_shutdown(self):
+        self.__server._trigger_shutdown()
+        self.assertTrue(self.__server.shutdown)
+
+    def test_sigterm_handler(self):
+        """Check the signal handler behavior.
+
+        SIGTERM and SIGINT should be caught and should call memmgr's
+        _trigger_shutdown().  This test also indirectly confirms main() calls
+        run_internal().
+
+        """
+        def checker():
+            self.__shutdown_called = True
+
+        self.__server._run_internal = lambda: os.kill(os.getpid(),
+                                                      signal.SIGTERM)
+        self.__server._trigger_shutdown = lambda: checker()
+        self.assertEqual(0, self.__server.run('test'))
+        self.assertTrue(self.__shutdown_called)
+
+        self.__shutdown_called = False
+        self.__server._run_internal = lambda: os.kill(os.getpid(),
+                                                      signal.SIGINT)
+        self.assertEqual(0, self.__server.run('test'))
+        self.assertTrue(self.__shutdown_called)
+
+    def test_exception(self):
+        """Check exceptions are handled, not leaked."""
+        def exception_raiser(ex_cls):
+            raise ex_cls('test')
+
+        # Test all possible exceptions that are explicitly caught
+        for ex in [TestException]:
+            self.__server._run_internal = lambda: exception_raiser(ex)
+            self.assertEqual(1, self.__server.run('test'))
+
+if __name__== "__main__":
+    isc.log.init("bind10_server_test")
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()