Parcourir la source

[2854] handle memmgr configuration; it's just mapped_file_dir for now.

JINMEI Tatuya il y a 12 ans
Parent
commit
983f11cfdd

+ 1 - 0
configure.ac

@@ -1382,6 +1382,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            src/bin/usermgr/b10-cmdctl-usermgr.py
            src/bin/usermgr/b10-cmdctl-usermgr.py
            src/bin/memmgr/memmgr.py
            src/bin/memmgr/memmgr.py
+           src/bin/memmgr/memmgr.spec.pre
            src/bin/msgq/msgq.py
            src/bin/msgq/msgq.py
            src/bin/msgq/run_msgq.sh
            src/bin/msgq/run_msgq.sh
            src/bin/auth/auth.spec.pre
            src/bin/auth/auth.spec.pre

+ 1 - 0
src/bin/memmgr/.gitignore

@@ -1,3 +1,4 @@
 /b10-memmgr
 /b10-memmgr
 /memmgr.py
 /memmgr.py
+/memmgr.spec
 /b10-memmgr.8
 /b10-memmgr.8

+ 4 - 1
src/bin/memmgr/Makefile.am

@@ -14,7 +14,7 @@ CLEANFILES = b10-memmgr memmgr.pyc
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.pyc
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.pyc
 
 
-EXTRA_DIST =  memmgr_messages.mes memmgr.spec
+EXTRA_DIST =  memmgr_messages.mes
 
 
 man_MANS = b10-memmgr.8
 man_MANS = b10-memmgr.8
 DISTCLEANFILES = $(man_MANS)
 DISTCLEANFILES = $(man_MANS)
@@ -38,6 +38,9 @@ $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py : memmgr_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message \
 	$(top_builddir)/src/lib/log/compiler/message \
 	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/memmgr_messages.mes
 	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/memmgr_messages.mes
 
 
+memmgr.spec: memmgr.spec.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" memmgr.spec.pre > $@
+
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
 b10-memmgr: memmgr.py $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
 b10-memmgr: memmgr.py $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
 	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" memmgr.py >$@
 	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" memmgr.py >$@

+ 64 - 0
src/bin/memmgr/memmgr.py.in

@@ -21,6 +21,7 @@ import signal
 
 
 sys.path.append('@@PYTHONPATH@@')
 sys.path.append('@@PYTHONPATH@@')
 import isc.log
 import isc.log
+#from isc.log import DBGLVL_TRACE_BASIC
 from isc.log_messages.memmgr_messages import *
 from isc.log_messages.memmgr_messages import *
 from isc.server_common.bind10_server import BIND10Server
 from isc.server_common.bind10_server import BIND10Server
 
 
@@ -29,8 +30,71 @@ MODULE_NAME = 'memmgr'
 isc.log.init('b10-memmgr', buffer=True)
 isc.log.init('b10-memmgr', buffer=True)
 logger = isc.log.Logger(MODULE_NAME)
 logger = isc.log.Logger(MODULE_NAME)
 
 
+class ConfigError(Exception):
+    """An exception class raised for configuration errors of Memmgr."""
+    pass
+
 class Memmgr(BIND10Server):
 class Memmgr(BIND10Server):
+    def __init__(self):
+        # configurable parameter: initially this is the only param, so
+        # we only maintain as a single attribute.  As the class is extended
+        # and more configurable, consider introducing a compound type or
+        # class.
+        # This is defined as "protected" so tests can inspect it; others
+        # shouldn't use it directly.
+        self._mapped_file_dir = None
+
     def _config_handler(self, new_config):
     def _config_handler(self, new_config):
+        """Configuration handler, called via BIND10Server.
+
+        This method must be exception free.  We assume minimum validity
+        about the parameter, though: it should be a valid dict, and conform
+        to the type specification of the spec file.
+
+        """
+        logger.debug(logger.DBGLVL_TRACE_BASIC, MEMMGR_CONFIG_UPDATE)
+
+        # Default answer:
+        answer = isc.config.create_answer(0)
+
+        # If this is the first time, initialize the local attributes with the
+        # latest full config data, which consist of the defaults with
+        # possibly overridden by user config.  Otherwise, just apply the latest
+        # diff.
+        if self._mapped_file_dir is None:
+            new_config = self.mod_ccsession.get_full_config()
+        try:
+            self.__update_config(new_config)
+        except Exception as ex:
+            logger.error(MEMMGR_CONFIG_FAIL, ex)
+            answer = isc.config.create_answer(
+                1, 'Memmgr failed to apply configuration updates: ' + str(ex))
+
+        return answer
+
+    def __update_config(self, new_config):
+        """Apply config changes to local attributes.
+
+        This is a subroutine of _config_handler.  It's supposed to provide
+        strong exception guarantee: either all changes successfully apply
+        or, if any error is found, none applies.  In the latter case the
+        entire original configuration should be kept.
+
+        Errors are to be reported as an exception.
+
+        """
+        new_mapped_file_dir = new_config.get('mapped_file_dir')
+        if new_mapped_file_dir is not None:
+            if not os.path.isdir(new_mapped_file_dir):
+                raise ConfigError('mapped_file_dir is not a directory: ' +
+                                  new_mapped_file_dir)
+            if not os.access(new_mapped_file_dir, os.W_OK):
+                raise ConfigError('mapped_file_dir is not writable: ' +
+                                  new_mapped_file_dir)
+            self._mapped_file_dir = new_mapped_file_dir
+
+    def _setup_module(self):
+        """Module specific initialization for BIND10Server."""
         pass
         pass
 
 
 if '__main__' == __name__:
 if '__main__' == __name__:

+ 5 - 0
src/bin/memmgr/memmgr.spec

@@ -2,6 +2,11 @@
   "module_spec": {
   "module_spec": {
     "module_name": "Memmgr",
     "module_name": "Memmgr",
     "config_data": [
     "config_data": [
+      { "item_name": "mapped_file_dir",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/mapped_files"
+      }
     ],
     ],
     "commands": [
     "commands": [
       {
       {

+ 8 - 1
src/bin/memmgr/memmgr_messages.mes

@@ -16,4 +16,11 @@
 # <topsrcdir>/tools/reorder_message_file.py to make sure the
 # <topsrcdir>/tools/reorder_message_file.py to make sure the
 # messages are in the correct order.
 # messages are in the correct order.
 
 
-% MEMMGR_CONFIG_UPDATE received new configuration: %1
+% MEMMGR_CONFIG_FAIL failed to apply configuration updates: %1
+The memmgr daemon tried to apply configuration updates but found an error.
+The cause of the error is included in the message.  None of the received
+updates applied, and the daemon keeps running with the previous configuration.
+
+% MEMMGR_CONFIG_UPDATE received new configuration
+A debug message.  The memmgr daemon receives configuratiopn updates
+and is now applying them to its running configurations.

+ 81 - 1
src/bin/memmgr/tests/memmgr_test.py

@@ -14,17 +14,97 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
 import unittest
 import unittest
+import os
+import re
 
 
 import isc.log
 import isc.log
 import isc.config
 import isc.config
+from isc.config import parse_answer
 import memmgr
 import memmgr
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+    def __init__(self, specfile, config_handler, command_handler):
+        super().__init__()
+        specfile = os.environ['B10_FROM_BUILD'] + '/src/bin/memmgr/memmgr.spec'
+        module_spec = isc.config.module_spec_from_file(specfile)
+        isc.config.ConfigData.__init__(self, module_spec)
+
+    def start(self):
+        pass
 
 
 class MockMemmgr(memmgr.Memmgr):
 class MockMemmgr(memmgr.Memmgr):
-    pass
+    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 TestMemmgr(unittest.TestCase):
 class TestMemmgr(unittest.TestCase):
     def setUp(self):
     def setUp(self):
+        # Some tests use this directory.  Make sure it doesn't pre-exist.
+        self.__test_mapped_file_dir = \
+            os.environ['B10_FROM_BUILD'] + \
+            '/src/bin/memmgr/tests/test_mapped_files'
+        if os.path.isdir(self.__test_mapped_file_dir):
+            os.rmdir(self.__test_mapped_file_dir)
+
         self.__mgr = MockMemmgr()
         self.__mgr = MockMemmgr()
+        # Fake some 'os' module functions for easier tests
+        self.__orig_os_access = os.access
+        self.__orig_isdir = os.path.isdir
+
+    def tearDown(self):
+        # Restore faked values
+        os.access = self.__orig_os_access
+        os.path.isdir = self.__orig_isdir
+
+        # If at test created a mapped-files directory, delete it.
+        if os.path.isdir(self.__test_mapped_file_dir):
+            os.rmdir(self.__test_mapped_file_dir)
+
+    def test_init(self):
+        self.assertIsNone(self.__mgr._mapped_file_dir)
+
+    def test_configure(self):
+        self.__mgr._setup_ccsession()
+
+        # Pretend specified directories exist and writable
+        os.path.isdir = lambda x: True
+        os.access = lambda x, y: True
+
+        # At the initial configuration, if mapped_file_dir isn't specified,
+        # the default value will be set.
+        self.assertEqual((0, None),
+                         parse_answer(self.__mgr._config_handler({})))
+        self.assertEqual('mapped_files',
+                         self.__mgr._mapped_file_dir.split('/')[-1])
+
+        # Update the configuration.
+        user_cfg = {'mapped_file_dir': '/some/path/dir'}
+        self.assertEqual((0, None),
+                         parse_answer(self.__mgr._config_handler(user_cfg)))
+        self.assertEqual('/some/path/dir', self.__mgr._mapped_file_dir)
+
+        # Bad update: diretory doesn't exist (we assume it really doesn't
+        # exist in the tested environment).  Update won't be made.
+        os.path.isdir = self.__orig_isdir # use real library
+        user_cfg = {'mapped_file_dir': '/another/path/dir'}
+        answer = parse_answer(self.__mgr._config_handler(user_cfg))
+        self.assertEqual(1, answer[0])
+        self.assertIsNotNone(re.search('not a directory', answer[1]))
+
+        # Bad update: directory exists but is not readable.
+        os.mkdir(self.__test_mapped_file_dir, 0o500) # drop writable bit
+        os.access = self.__orig_os_access
+        user_cfg = {'mapped_file_dir': self.__test_mapped_file_dir}
+        answer = parse_answer(self.__mgr._config_handler(user_cfg))
+        self.assertEqual(1, answer[0])
+        self.assertIsNotNone(re.search('not writable', answer[1]))
 
 
 if __name__== "__main__":
 if __name__== "__main__":
     isc.log.resetUnitTestRootLogger()
     isc.log.resetUnitTestRootLogger()