From: JINMEI Tatuya Date: Mon, 10 Jun 2013 17:58:41 +0000 (-0700) Subject: [2854] handle memmgr configuration; it's just mapped_file_dir for now. X-Git-Tag: bind10-1.2.0beta1-release~378^2~20 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=983f11cfdd11153b234ff7626c5ff1dccb1baeb2;p=thirdparty%2Fkea.git [2854] handle memmgr configuration; it's just mapped_file_dir for now. --- diff --git a/configure.ac b/configure.ac index ac1f756e74..285d09e74c 100644 --- a/configure.ac +++ b/configure.ac @@ -1382,6 +1382,7 @@ AC_OUTPUT([doc/version.ent src/bin/usermgr/run_b10-cmdctl-usermgr.sh src/bin/usermgr/b10-cmdctl-usermgr.py src/bin/memmgr/memmgr.py + src/bin/memmgr/memmgr.spec.pre src/bin/msgq/msgq.py src/bin/msgq/run_msgq.sh src/bin/auth/auth.spec.pre diff --git a/src/bin/memmgr/.gitignore b/src/bin/memmgr/.gitignore index 9b12294ee7..3743d584db 100644 --- a/src/bin/memmgr/.gitignore +++ b/src/bin/memmgr/.gitignore @@ -1,3 +1,4 @@ /b10-memmgr /memmgr.py +/memmgr.spec /b10-memmgr.8 diff --git a/src/bin/memmgr/Makefile.am b/src/bin/memmgr/Makefile.am index 8fc1cdcf8d..94a380a0ea 100644 --- a/src/bin/memmgr/Makefile.am +++ b/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.pyc -EXTRA_DIST = memmgr_messages.mes memmgr.spec +EXTRA_DIST = memmgr_messages.mes man_MANS = b10-memmgr.8 DISTCLEANFILES = $(man_MANS) @@ -38,6 +38,9 @@ $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py : memmgr_messages.mes $(top_builddir)/src/lib/log/compiler/message \ -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 b10-memmgr: memmgr.py $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" memmgr.py >$@ diff --git a/src/bin/memmgr/memmgr.py.in b/src/bin/memmgr/memmgr.py.in index c59e7f36a9..99a5c1a89b 100755 --- a/src/bin/memmgr/memmgr.py.in +++ b/src/bin/memmgr/memmgr.py.in @@ -21,6 +21,7 @@ import signal sys.path.append('@@PYTHONPATH@@') import isc.log +#from isc.log import DBGLVL_TRACE_BASIC from isc.log_messages.memmgr_messages import * from isc.server_common.bind10_server import BIND10Server @@ -29,8 +30,71 @@ MODULE_NAME = 'memmgr' isc.log.init('b10-memmgr', buffer=True) logger = isc.log.Logger(MODULE_NAME) +class ConfigError(Exception): + """An exception class raised for configuration errors of Memmgr.""" + pass + 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): + """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 if '__main__' == __name__: diff --git a/src/bin/memmgr/memmgr.spec b/src/bin/memmgr/memmgr.spec.pre.in similarity index 67% rename from src/bin/memmgr/memmgr.spec rename to src/bin/memmgr/memmgr.spec.pre.in index 09a1971d76..6729690512 100644 --- a/src/bin/memmgr/memmgr.spec +++ b/src/bin/memmgr/memmgr.spec.pre.in @@ -2,6 +2,11 @@ "module_spec": { "module_name": "Memmgr", "config_data": [ + { "item_name": "mapped_file_dir", + "item_type": "string", + "item_optional": true, + "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/mapped_files" + } ], "commands": [ { diff --git a/src/bin/memmgr/memmgr_messages.mes b/src/bin/memmgr/memmgr_messages.mes index df0872d261..30ee52ed0b 100644 --- a/src/bin/memmgr/memmgr_messages.mes +++ b/src/bin/memmgr/memmgr_messages.mes @@ -16,4 +16,11 @@ # /tools/reorder_message_file.py to make sure the # 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. diff --git a/src/bin/memmgr/tests/memmgr_test.py b/src/bin/memmgr/tests/memmgr_test.py index 82f5906dd4..eb7a9d882b 100755 --- a/src/bin/memmgr/tests/memmgr_test.py +++ b/src/bin/memmgr/tests/memmgr_test.py @@ -14,17 +14,97 @@ # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import unittest +import os +import re import isc.log import isc.config +from isc.config import parse_answer 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): - 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): 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() + # 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__": isc.log.resetUnitTestRootLogger()