Commit 29920cfb authored by Jelte Jansen's avatar Jelte Jansen
Browse files

documentation and tests for configuration manager


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@865 e5f2f494-b856-4b98-b285-d166d9295462
parent 61cadb10
......@@ -21,9 +21,20 @@
# modeled after ccsession.h/cc 'protocol' changes here need to be
# made there as well
"""This module provides the ModuleCCSession and UICCSession classes,
"""Classes and functions for handling configuration and commands
This module provides the ModuleCCSession and UICCSession classes,
as well as a set of utility functions to create and parse messages
related to commands and configuration"""
related to commands and configuration
Modules should use the ModuleCCSession class to connect to the
configuration manager, and receive updates and commands from
other modules.
Configuration user interfaces should use the UICCSession to connect
to b10-cmdctl, and receive and send configuration and commands
through that to the configuration manager.
"""
from isc.cc import Session
from isc.config.config_data import ConfigData, MultiConfigData
......
......@@ -13,9 +13,11 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# This is the main class for the b10-cfgmgr daemon
#
"""This is the BIND 10 configuration manager, run by b10-cfgmgr.
It stores the system configuration, and sends updates of the
configuration to the modules that need them.
"""
import isc
import signal
......@@ -25,12 +27,19 @@ import os
from isc.cc import data
class ConfigManagerDataReadError(Exception):
"""This exception is thrown when there is an error while reading
the current configuration on startup."""
pass
class ConfigManagerDataEmpty(Exception):
"""This exception is thrown when the currently stored configuration
is not found, or appears empty."""
pass
class ConfigManagerData:
"""This class hold the actual configuration information, and
reads it from and writes it to persistent storage"""
CONFIG_VERSION = 1
def __init__(self, data_path, file_name = "b10-config.db"):
......@@ -43,11 +52,6 @@ class ConfigManagerData:
self.data_path = data_path
self.db_filename = data_path + os.sep + file_name
def set_data_definition(self, module_name, module_data_definition):
"""Set the data definition for the given module name."""
#self.zones[module_name] = module_data_definition
self.data_definitions[module_name] = module_data_definition
def read_from_file(data_path, file_name = "b10-config.db"):
"""Read the current configuration found in the file at
data_path. If the file does not exist, a
......@@ -106,10 +110,11 @@ class ConfigManager:
The ability to specify a custom session is for testing purposes
and should not be needed for normal usage."""
def __init__(self, data_path, session = None):
# remove these and use self.module_specs
#self.commands = {}
self.data_definitions = {}
"""Initialize the configuration manager. The data_path string
is the path to the directory where the configuration is
stored (in <data_path>/b10-config.db). Session is an optional
cc-channel session. If this is not given, a new one is
created"""
self.data_path = data_path
self.module_specs = {}
self.config = ConfigManagerData(data_path)
......@@ -126,15 +131,23 @@ class ConfigManager:
self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
def set_module_spec(self, spec):
#data_def = isc.config.ModuleSpec(spec)
"""Adds a ModuleSpec"""
self.module_specs[spec.get_module_name()] = spec
def remove_module_spec(self, module_name):
"""Removes the full ModuleSpec for the given module_name.
Does nothing if the module was not present."""
if module_name in self.module_specs:
del self.module_specs[module_name]
def get_module_spec(self, module_name):
"""Returns the full ModuleSpec for the module with the given
module_name"""
if module_name in self.module_specs:
return self.module_specs[module_name]
def get_config_spec(self, name = None):
"""Returns a dict containing 'module_name': config_data for
"""Returns a dict containing 'module_name': config_spec for
all modules. If name is specified, only that module will
be included"""
config_data = {}
......@@ -147,7 +160,7 @@ class ConfigManager:
return config_data
def get_commands_spec(self, name = None):
"""Returns a dict containing 'module_name': commands_dict for
"""Returns a dict containing 'module_name': commands_spec for
all modules. If name is specified, only that module will
be included"""
commands = {}
......@@ -174,6 +187,7 @@ class ConfigManager:
self.config.write_to_file()
def _handle_get_module_spec(self, cmd):
"""Private function that handles the 'get_module_spec' command"""
answer = {}
if len(cmd) > 1:
if type(cmd[1]) == dict:
......@@ -189,6 +203,7 @@ class ConfigManager:
return answer
def _handle_get_config(self, cmd):
"""Private function that handles the 'get_config' command"""
answer = {}
if len(cmd) > 1:
if type(cmd[1]) == dict:
......@@ -209,6 +224,7 @@ class ConfigManager:
return answer
def _handle_set_config(self, cmd):
"""Private function that handles the 'set_config' command"""
answer = None
if len(cmd) == 3:
# todo: use api (and check the data against the definition?)
......@@ -257,6 +273,7 @@ class ConfigManager:
return answer
def _handle_module_spec(self, spec):
"""Private function that handles the 'module_spec' command"""
# todo: validate? (no direct access to spec as
# todo: use ModuleSpec class
# todo: error checking (like keyerrors)
......@@ -271,7 +288,7 @@ class ConfigManager:
return answer
def handle_msg(self, msg):
"""Handle a direct command"""
"""Handle a command from the cc channel to the configuration manager"""
answer = {}
if "command" in msg:
cmd = msg["command"]
......@@ -306,6 +323,7 @@ class ConfigManager:
return answer
def run(self):
"""Runs the configuration manager."""
self.running = True
while (self.running):
msg, env = self.cc.group_recvmsg(False)
......@@ -314,10 +332,3 @@ class ConfigManager:
self.cc.group_reply(env, answer)
else:
self.running = False
cm = None
def signal_handler(signal, frame):
global cm
if cm:
cm.running = False
......@@ -35,9 +35,6 @@ class TestConfigManagerData(unittest.TestCase):
self.assertEqual(self.config_manager_data.db_filename,
self.data_path + os.sep + "b10-config.db")
def test_set_data_definition(self):
pass
def test_read_from_file(self):
ConfigManagerData.read_from_file(self.data_path)
self.assertRaises(ConfigManagerDataEmpty,
......@@ -59,6 +56,21 @@ class TestConfigManagerData(unittest.TestCase):
new_config = ConfigManagerData(self.data_path, output_file_name)
self.assertEqual(self.config_manager_data, new_config)
def test_equality(self):
# tests the __eq__ function. Equality is only defined
# by equality of the .data element. If data_path or db_filename
# are different, but the contents are the same, it's still
# considered equal
cfd1 = ConfigManagerData(self.data_path)
cfd2 = ConfigManagerData(self.data_path)
self.assertEqual(cfd1, cfd2)
cfd2.data_path = "some/unknown/path"
self.assertEqual(cfd1, cfd2)
cfd2.db_filename = "bad_file.name"
self.assertEqual(cfd1, cfd2)
cfd2.data['test'] = { 'a': [ 1, 2, 3]}
self.assertNotEqual(cfd1, cfd2)
#
# We can probably use a more general version of this
#
......@@ -126,6 +138,71 @@ class TestConfigManager(unittest.TestCase):
# this one is actually wrong, but 'current status quo'
self.assertEqual(msg, {"running": "configmanager"})
def test_set_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
def test_remove_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
self.cm.remove_module_spec(module_spec.get_module_name())
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
def test_get_module_spec(self):
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
module_spec2 = self.cm.get_module_spec(module_spec.get_module_name())
self.assertEqual(module_spec, module_spec2)
def test_get_config_spec(self):
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec, {})
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec, { 'Spec1': None })
self.cm.remove_module_spec('Spec1')
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
config_spec = self.cm.get_config_spec()
self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())
def test_get_commands_spec(self):
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec, {})
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec, { 'Spec1': None })
self.cm.remove_module_spec('Spec1')
module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
self.cm.set_module_spec(module_spec)
self.assert_(module_spec.get_module_name() in self.cm.module_specs)
commands_spec = self.cm.get_commands_spec()
self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())
def test_read_config(self):
self.assertEqual(self.cm.config.data, {'version': 1})
self.cm.read_config()
self.assertEqual(self.cm.config.data, {'TestModule': {'test': 124}, 'version': 1})
def test_write_config(self):
# tested in ConfigManagerData tests
pass
def _handle_msg_helper(self, msg, expected_answer):
answer = self.cm.handle_msg(msg)
self.assertEqual(expected_answer, answer)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment