From 03515c9829f7cd031854f360d67895ea9d8c83df Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 8 Feb 2010 09:53:45 +0000 Subject: [PATCH 01/81] created new branch for configuration changes removed obsolete datasource branch git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@745 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/config_test.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/config/python/isc/config/config_test.in b/src/lib/config/python/isc/config/config_test.in index d35e307731..8ad1eaa8f7 100755 --- a/src/lib/config/python/isc/config/config_test.in +++ b/src/lib/config/python/isc/config/config_test.in @@ -12,5 +12,7 @@ CONFIG_TESTDATA_PATH=@abs_top_srcdir@/src/lib/config/testdata export CONFIG_TESTDATA_PATH cd ${BIND10_PATH} -exec ${PYTHON_EXEC} -O ${CONFIG_PATH}/datadefinition_test.py $* +${PYTHON_EXEC} -O ${CONFIG_PATH}/datadefinition_test.py $* + +${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $* From d21bec5c81a82f42777ed589999646f0782b6e7f Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 8 Feb 2010 11:15:08 +0000 Subject: [PATCH 02/81] reply var could be uninitialized, so set to None before doing len check git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@746 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/cmdctl/b10-cmdctl.py.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/cmdctl/b10-cmdctl.py.in b/src/bin/cmdctl/b10-cmdctl.py.in index 9737d5fa3f..3510a37b77 100644 --- a/src/bin/cmdctl/b10-cmdctl.py.in +++ b/src/bin/cmdctl/b10-cmdctl.py.in @@ -162,6 +162,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): param = None len = self.headers.get('Content-Length') rcode = http.client.OK + reply = None if len: post_str = str(self.rfile.read(int(len)).decode()) print("command parameter:%s" % post_str) From 15c1f8bc217a98ca78b6b3d920dffe84948f5f09 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 8 Feb 2010 12:04:23 +0000 Subject: [PATCH 03/81] tests for ConfigManagerData git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@747 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/cfgmgr.py | 51 +++++++--- .../config/python/isc/config/cfgmgr_test.py | 92 +++++++++++++++++++ .../python/isc/config/datadefinition_test.py | 3 + src/lib/config/testdata/b10-config-bad1.db | 1 + src/lib/config/testdata/b10-config-bad2.db | 1 + src/lib/config/testdata/b10-config-bad3.db | 0 src/lib/config/testdata/b10-config.db | 1 + 7 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 src/lib/config/python/isc/config/cfgmgr_test.py create mode 100644 src/lib/config/testdata/b10-config-bad1.db create mode 100644 src/lib/config/testdata/b10-config-bad2.db create mode 100644 src/lib/config/testdata/b10-config-bad3.db create mode 100644 src/lib/config/testdata/b10-config.db diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 1cceacb369..93688112a3 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -5,21 +5,40 @@ import pprint import os from isc.cc import data +class ConfigManagerDataReadError(Exception): + pass + +class ConfigManagerDataEmpty(Exception): + pass + class ConfigManagerData: CONFIG_VERSION = 1 - def __init__(self, data_path): + def __init__(self, data_path, file_name = "b10-config.db"): + """Initialize the data for the configuration manager, and + set the version and path for the data store. Initializing + this does not yet read the database, a call to + read_from_file is needed for that.""" self.data = {} self.data['version'] = ConfigManagerData.CONFIG_VERSION self.data_path = data_path - self.db_filename = data_path + "/b10-config.db" + self.db_filename = data_path + os.sep + file_name def set_data_definition(self, module_name, module_data_definition): - self.zones[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): - config = ConfigManagerData(data_path) + 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 + ConfigManagerDataEmpty exception is raised. If there is a + parse error, or if the data in the file has the wrong + version, a ConfigManagerDataReadError is raised. In the first + case, it is probably safe to log and ignore. In the case of + the second exception, the best way is probably to report the + error and stop loading the system.""" + config = ConfigManagerData(data_path, file_name) try: file = open(config.db_filename, 'r') file_config = ast.literal_eval(file.read()) @@ -27,19 +46,20 @@ class ConfigManagerData: file_config['version'] == ConfigManagerData.CONFIG_VERSION: config.data = file_config else: - # of course we can put in a migration path here for old data - print("[bind-cfgd] Old version of data found, starting with empty configuration") + # We can put in a migration path here for old data + raise ConfigManagerDataReadError("[bind-cfgd] Old version of data found") file.close() except IOError as ioe: - print("No config file found, starting with empty config") - except EOFError as eofe: - print("Config file empty, starting with empty config") + raise ConfigManagerDataEmpty("No config file found") except: - print("Config file unreadable, starting with empty config") + raise ConfigManagerDataReadError("Config file unreadable") return config - def write_to_file(self): + def write_to_file(self, output_file_name = None): + """Writes the current configuration data to a file. If + output_file_name is not specified, the file used in + read_from_file is used.""" try: tmp_filename = self.db_filename + ".tmp" file = open(tmp_filename, 'w'); @@ -52,6 +72,13 @@ class ConfigManagerData: except IOError as ioe: print("Unable to write config file; configuration not stored") + def __eq__(self, other): + """Returns True if the data contained is equal. data_path and + db_filename may be different.""" + if type(other) != type(self): + return False + return self.data == other.data + class ConfigManager: def __init__(self, data_path): self.commands = {} diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py new file mode 100644 index 0000000000..e98f2f782a --- /dev/null +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -0,0 +1,92 @@ +# Copyright (C) 2009 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. + +# +# Tests for the configuration manager module +# + +import unittest +import os +from isc.config.cfgmgr import * + +class TestConfigManagerData(unittest.TestCase): + def setUp(self): + self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + self.config_manager_data = ConfigManagerData(self.data_path) + self.assert_(self.config_manager_data) + + def test_init(self): + self.assertEqual(self.config_manager_data.data['version'], + ConfigManagerData.CONFIG_VERSION) + self.assertEqual(self.config_manager_data.data_path, + self.data_path) + 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, + ConfigManagerData.read_from_file, + "doesnotexist") + self.assertRaises(ConfigManagerDataReadError, + ConfigManagerData.read_from_file, + self.data_path, "b10-config-bad1.db") + self.assertRaises(ConfigManagerDataReadError, + ConfigManagerData.read_from_file, + self.data_path, "b10-config-bad2.db") + self.assertRaises(ConfigManagerDataReadError, + ConfigManagerData.read_from_file, + self.data_path, "b10-config-bad3.db") + + def test_write_to_file(self): + output_file_name = "b10-config-write-test"; + self.config_manager_data.write_to_file(output_file_name) + new_config = ConfigManagerData(self.data_path, output_file_name) + self.assertEqual(self.config_manager_data, new_config) + + +class TestConfigManager: + + def setUp(self): + pass + + def test_init(self): + pass + + def test_set_config(self): + pass + + def test_remove_config(self): + pass + + def test_write_config(self): + pass + + def test_handle_msg(self): + pass + + def test_run(self): + pass + + +if __name__ == '__main__': + if not 'CONFIG_TESTDATA_PATH' in os.environ: + print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files") + exit(1) + unittest.main() + diff --git a/src/lib/config/python/isc/config/datadefinition_test.py b/src/lib/config/python/isc/config/datadefinition_test.py index 15de0f16b4..ead57fde72 100644 --- a/src/lib/config/python/isc/config/datadefinition_test.py +++ b/src/lib/config/python/isc/config/datadefinition_test.py @@ -89,4 +89,7 @@ class TestDataDefinition(unittest.TestCase): self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data")) if __name__ == '__main__': + if not 'CONFIG_TESTDATA_PATH' in os.environ: + print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files") + exit(1) unittest.main() diff --git a/src/lib/config/testdata/b10-config-bad1.db b/src/lib/config/testdata/b10-config-bad1.db new file mode 100644 index 0000000000..e64b39d103 --- /dev/null +++ b/src/lib/config/testdata/b10-config-bad1.db @@ -0,0 +1 @@ +{'version': 0} diff --git a/src/lib/config/testdata/b10-config-bad2.db b/src/lib/config/testdata/b10-config-bad2.db new file mode 100644 index 0000000000..fa2749a23f --- /dev/null +++ b/src/lib/config/testdata/b10-config-bad2.db @@ -0,0 +1 @@ +{'version': diff --git a/src/lib/config/testdata/b10-config-bad3.db b/src/lib/config/testdata/b10-config-bad3.db new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/lib/config/testdata/b10-config.db b/src/lib/config/testdata/b10-config.db new file mode 100644 index 0000000000..2a07bbae3c --- /dev/null +++ b/src/lib/config/testdata/b10-config.db @@ -0,0 +1 @@ +{'version': 1} From 877a52f1c944b8a0dcde19f7ec6b99a2ae273eba Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 8 Feb 2010 16:31:00 +0000 Subject: [PATCH 04/81] bit of refactoring in cfgmgr.py added tests for ConfigManager class also added in these tests a fake command session class, which we might want to generalize for other tests git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@748 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/cfgmgr.py | 190 ++++++++++-------- .../config/python/isc/config/cfgmgr_test.py | 128 +++++++++++- 2 files changed, 231 insertions(+), 87 deletions(-) diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 93688112a3..e14bd29268 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -80,137 +80,167 @@ class ConfigManagerData: return self.data == other.data class ConfigManager: - def __init__(self, data_path): + """Creates a configuration manager. The data_path is the path + to the directory containing the b10-config.db file. + If session is set, this will be used as the communication + channel session. If not, a new session will be created. + 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): self.commands = {} self.data_definitions = {} self.data_path = data_path self.config = ConfigManagerData(data_path) - self.cc = isc.cc.Session() + if session: + self.cc = session + else: + self.cc = isc.cc.Session() self.cc.group_subscribe("ConfigManager") self.cc.group_subscribe("Boss", "ConfigManager") self.running = False def notify_boss(self): + """Notifies the Boss module that the Config Manager is running""" self.cc.group_sendmsg({"running": "configmanager"}, "Boss") def set_config(self, module_name, data_specification): + """Set the data specification for the given module""" self.data_definitions[module_name] = data_specification def remove_config(self, module_name): + """Remove the data specification for the given module""" self.data_definitions[module_name] def set_commands(self, module_name, commands): + """Set the command list for the given module""" self.commands[module_name] = commands def remove_commands(self, module_name): + """Remove the command list for the given module""" del self.commands[module_name] def read_config(self): - print("Reading config") + """Read the current configuration from the b10-config.db file + at the path specificied at init()""" self.config = ConfigManagerData.read_from_file(self.data_path) def write_config(self): - print("Writing config") + """Write the current configuration to the b10-config.db file + at the path specificied at init()""" self.config.write_to_file() + def _handle_get_data_spec(self, cmd): + answer = {} + if len(cmd) > 1: + if type(cmd[1]) == dict: + if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': + module_name = cmd[1]['module_name'] + try: + answer["result"] = [0, self.data_definitions[module_name]] + except KeyError as ke: + answer["result"] = [1, "No specification for module " + module_name] + else: + answer["result"] = [1, "Bad module_name in get_data_spec command"] + else: + answer["result"] = [1, "Bad get_data_spec command, argument not a dict"] + else: + answer["result"] = [0, self.data_definitions] + return answer + + def _handle_get_config(self, cmd): + answer = {} + if len(cmd) > 1: + if type(cmd[1]) == dict: + if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': + module_name = cmd[1]['module_name'] + try: + answer["result"] = [0, data.find(self.config.data, module_name) ] + except data.DataNotFoundError as dnfe: + # no data is ok, that means we have nothing that + # deviates from default values + answer["result"] = [0, {} ] + else: + answer["result"] = [1, "Bad module_name in get_config command"] + else: + answer["result"] = [1, "Bad get_config command, argument not a dict"] + else: + answer["result"] = [0, self.config.data] + return answer + + def _handle_set_config(self, cmd): + answer = {} + if len(cmd) == 3: + # todo: use api (and check the data against the definition?) + module_name = cmd[1] + conf_part = data.find_no_exc(self.config.data, module_name) + if conf_part: + data.merge(conf_part, cmd[2]) + self.cc.group_sendmsg({ "config_update": conf_part }, module_name) + else: + conf_part = data.set(self.config.data, module_name, {}) + data.merge(conf_part[module_name], cmd[2]) + # send out changed info + self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) + self.write_config() + answer["result"] = [ 0 ] + elif len(cmd) == 2: + # todo: use api (and check the data against the definition?) + data.merge(self.config.data, cmd[1]) + # send out changed info + for module in self.config.data: + if module != "version": + self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) + self.write_config() + answer["result"] = [ 0 ] + else: + answer["result"] = [ 1, "Wrong number of arguments" ] + return answer + + def _handle_data_specification(self, spec): + # todo: validate? (no direct access to spec as + # todo: use DataDefinition class + # todo: error checking (like keyerrors) + answer = {} + if "config_data" in spec: + self.set_config(spec["module_name"], spec["config_data"]) + self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "Cmd-Ctrld") + if "commands" in spec: + self.set_commands(spec["module_name"], spec["commands"]) + self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "Cmd-Ctrld") + answer["result"] = [ 0 ] + return answer + def handle_msg(self, msg): - """return answer message""" + """Handle a direct command""" answer = {} if "command" in msg: cmd = msg["command"] try: if cmd[0] == "get_commands": - print("[XX] bind-cfgd got get_commands"); - print("[XX] sending:") - print(self.commands) answer["result"] = [ 0, self.commands ] + elif cmd[0] == "get_data_spec": - if len(cmd) > 1 and cmd[1]['module_name'] != '': - module_name = cmd[1]['module_name'] - try: - answer["result"] = [0, self.data_definitions[module_name]] - except KeyError as ke: - answer["result"] = [1, "No specification for module " + module_name] - else: - answer["result"] = [0, self.data_definitions] + answer = self._handle_get_data_spec(cmd) elif cmd[0] == "get_config": - # we may not have any configuration here - conf_part = None - print("[XX] bind-cfgd got command:") - print(cmd) - if len(cmd) > 1: - try: - conf_part = data.find(self.config.data, cmd[1]['module_name']) - except data.DataNotFoundError as dnfe: - pass - else: - conf_part = self.config.data - if conf_part: - answer["result"] = [ 0, conf_part ] - else: - answer["result"] = [ 0 ] - + answer = self._handle_get_config(cmd) elif cmd[0] == "set_config": - if len(cmd) == 3: - # todo: use api (and check types?) - if cmd[1] != "": - conf_part = data.find_no_exc(self.config.data, cmd[1]) - if not conf_part: - conf_part = data.set(self.config.data, cmd[1], "") - else: - conf_part = self.config.data - data.merge(conf_part, cmd[2]) - print("[XX bind-cfgd] new config (part):") - print(conf_part) - # send out changed info - self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1]) - self.write_config() - answer["result"] = [ 0 ] - elif len(cmd) == 2: - print("[XX bind-cfgd] old config:") - print(self.config.data) - print("[XX bind-cfgd] updating with:") - print(cmd[1]) - # TODO: ask module to check the config first... - data.merge(self.config.data, cmd[1]) - print("[XX bind-cfgd] new config:") - print(self.config.data) - # send out changed info - for module in self.config.data: - self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) - self.write_config() - answer["result"] = [ 0 ] - else: - answer["result"] = [ 1, "Wrong number of arguments" ] - + answer = self._handle_set_config(cmd) elif cmd[0] == "shutdown": print("[bind-cfgd] Received shutdown command") self.running = False + answer["result"] = [ 0 ] else: - print("[bind-cfgd] unknown command: " + str(cmd)) answer["result"] = [ 1, "Unknown command: " + str(cmd) ] except IndexError as ie: - print("[bind-cfgd] missing argument") answer["result"] = [ 1, "Missing argument in command: " + str(ie) ] raise ie elif "data_specification" in msg: - # todo: validate? (no direct access to spec as - # todo: use DataDefinition class? - print("[XX] bind-cfgd got specification:") - print(msg["data_specification"]) - spec = msg["data_specification"] - if "config_data" in spec: - self.set_config(spec["module_name"], spec["config_data"]) - self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "Cmd-Ctrld") - if "commands" in spec: - self.set_commands(spec["module_name"], spec["commands"]) - self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "Cmd-Ctrld") - answer["result"] = [ 0 ] + answer = self._handle_data_specification(msg["data_specification"]) elif 'result' in msg: + # this seems wrong, might start pingpong answer['result'] = [0] else: - print("[bind-cfgd] unknown message: " + str(msg)) - answer["result"] = [ 1, "Unknown module: " + str(msg) ] + answer["result"] = [ 1, "Unknown message format: " + str(msg) ] return answer def run(self): diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index e98f2f782a..60c6214fbf 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -59,26 +59,140 @@ class TestConfigManagerData(unittest.TestCase): new_config = ConfigManagerData(self.data_path, output_file_name) self.assertEqual(self.config_manager_data, new_config) +# +# We can probably use a more general version of this +# +class FakeCCSession: + def __init__(self): + self.subscriptions = {} + # each entry is of the form [ channel, instance, message ] + self.message_queue = [] -class TestConfigManager: + def group_subscribe(self, group_name, instance_name = None): + if not group_name in self.subscriptions: + self.subscriptions[group_name] = [] + if instance_name: + self.subscriptions[group_name].append(instance_name) + + + def has_subscription(self, group_name, instance_name = None): + if group_name in self.subscriptions: + if instance_name: + return instance_name in self.subscriptions[group_name] + else: + return True + else: + return False + + def group_sendmsg(self, msg, channel, target = None): + self.message_queue.append([ channel, target, msg ]) + + def get_message(self, channel, target = None): + for qm in self.message_queue: + if qm[0] == channel and qm[1] == target: + self.message_queue.remove(qm) + return qm[2] + return None + + +class TestConfigManager(unittest.TestCase): def setUp(self): - pass + self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + self.fake_session = FakeCCSession() + self.cm = ConfigManager(self.data_path, self.fake_session) + self.name = "TestModule" + self.spec = { "asdf" } + self.commands = { "bbbb" } def test_init(self): - pass + self.assert_(self.cm.commands == {}) + self.assert_(self.cm.data_definitions == {}) + self.assert_(self.cm.data_path == self.data_path) + self.assert_(self.cm.config != None) + self.assert_(self.fake_session.has_subscription("ConfigManager")) + self.assert_(self.fake_session.has_subscription("Boss", "ConfigManager")) + self.assertFalse(self.cm.running) + + def test_notify_boss(self): + self.cm.notify_boss() + msg = self.fake_session.get_message("Boss", None) + self.assert_(msg) + # this one is actually wrong, but 'current status quo' + self.assertEqual(msg, {"running": "configmanager"}) def test_set_config(self): - pass + self.cm.set_config(self.name, self.spec) + self.assertEqual(self.cm.data_definitions[self.name], self.spec) def test_remove_config(self): - pass + self.assertRaises(KeyError, self.cm.remove_config, self.name) + self.cm.set_config(self.name, self.spec) + self.cm.remove_config(self.name) + + def test_set_commands(self): + self.cm.set_commands(self.name, self.commands) + self.assertEqual(self.cm.commands[self.name], self.commands) def test_write_config(self): - pass + self.assertRaises(KeyError, self.cm.remove_commands, self.name) + self.cm.set_commands(self.name, self.commands) + self.cm.remove_commands(self.name) + + def _handle_msg_helper(self, msg, expected_answer): + answer = self.cm.handle_msg(msg) + #self.assertEquals(answer, expected_answer) + self.assertEqual(expected_answer, answer) def test_handle_msg(self): - pass + self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']}) + self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']}) + self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: ['badcommand']"]}) + self._handle_msg_helper({ "command": [ "get_commands" ] }, { 'result': [ 0, {} ]}) + self._handle_msg_helper({ "command": [ "get_data_spec" ] }, { 'result': [ 0, {} ]}) + self._handle_msg_helper({ "command": [ "get_data_spec", { "module_name": "nosuchmodule" } ] }, + {'result': [1, 'No specification for module nosuchmodule']}) + self._handle_msg_helper({ "command": [ "get_data_spec", 1 ] }, + {'result': [1, 'Bad get_data_spec command, argument not a dict']}) + self._handle_msg_helper({ "command": [ "get_data_spec", { } ] }, + {'result': [1, 'Bad module_name in get_data_spec command']}) + self._handle_msg_helper({ "command": [ "get_config" ] }, { 'result': [ 0, { 'version': 1} ]}) + self._handle_msg_helper({ "command": [ "get_config", { "module_name": "nosuchmodule" } ] }, + {'result': [0, {}]}) + self._handle_msg_helper({ "command": [ "get_config", 1 ] }, + {'result': [1, 'Bad get_config command, argument not a dict']}) + self._handle_msg_helper({ "command": [ "get_config", { } ] }, + {'result': [1, 'Bad module_name in get_config command']}) + self._handle_msg_helper({ "command": [ "set_config" ] }, + {'result': [1, 'Wrong number of arguments']}) + self._handle_msg_helper({ "command": [ "set_config", {} ] }, + {'result': [0]}) + self.assertEqual(len(self.fake_session.message_queue), 0) + self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 123 } ] }, + {'result': [0]}) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 123}}, + self.fake_session.get_message(self.name, None)) + self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 124 } ] }, + {'result': [0]}) + #print(self.fake_session.message_queue) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 124}}, + self.fake_session.get_message(self.name, None)) + self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) + self._handle_msg_helper({ "data_specification": + { "module_name": self.name, "config_data": self.spec, "commands": self.commands } + }, + {'result': [0]}) + self.assertEqual(len(self.fake_session.message_queue), 2) + # the name here is actually wrong (and hardcoded), but needed in the current version + # TODO: fix that + self.assertEqual({'specification_update': [ self.name, self.spec ] }, + self.fake_session.get_message("Cmd-Ctrld", None)) + self.assertEqual({'commands_update': [ self.name, self.commands ] }, + self.fake_session.get_message("Cmd-Ctrld", None)) + + def test_run(self): pass From 04f3eb1b3eb50b4d157afdab64acb540a158196c Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 9 Feb 2010 16:12:41 +0000 Subject: [PATCH 05/81] cfgmgr handle non-existent db file update copyright statements added tests for data element helper functions improved and cleaned up data element helper functions git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@773 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/cc/python/isc/cc/data.py | 88 ++++++++++++--- src/lib/cc/python/isc/cc/data_test.py | 106 ++++++++++++++++++ src/lib/config/python/isc/config/cfgmgr.py | 25 ++++- .../config/python/isc/config/cfgmgr_test.py | 2 +- 4 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 src/lib/cc/python/isc/cc/data_test.py diff --git a/src/lib/cc/python/isc/cc/data.py b/src/lib/cc/python/isc/cc/data.py index 475832c020..d5c6f79bd9 100644 --- a/src/lib/cc/python/isc/cc/data.py +++ b/src/lib/cc/python/isc/cc/data.py @@ -1,5 +1,25 @@ -# data, data_definition, config_data, module_config_data and ui_config_data classes -# we might want to split these up :) +# Copyright (C) 2010 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. + +# +# Helper functions for data elements as used in cc-channel and +# configuration. There is no python equivalent for the cpp Element +# class, since data elements are represented by native python types +# (int, real, bool, string, list and dict respectively) +# + import ast class DataNotFoundError(Exception): pass @@ -9,6 +29,8 @@ def merge(orig, new): """Merges the contents of new into orig, think recursive update() orig and new must both be dicts. If an element value is None in new it will be removed in orig.""" + if type(orig) != dict or type(new) != dict: + raise DataTypeError("Not a dict in merge()") for kn in new.keys(): if kn in orig: if new[kn]: @@ -23,6 +45,10 @@ def merge(orig, new): def find(element, identifier): """Returns the subelement in the given data element, raises DataNotFoundError if not found""" + if type(identifier) != str or (type(element) != dict and identifier != ""): + raise DataTypeError("identifier in merge() is not a string") + if type(identifier) != str or (type(element) != dict and identifier != ""): + raise DataTypeError("element in merge() is not a dict") id_parts = identifier.split("/") id_parts[:] = (value for value in id_parts if value != "") cur_el = element @@ -34,6 +60,17 @@ def find(element, identifier): return cur_el def set(element, identifier, value): + """Sets the value at the element specified by identifier to value. + If the value is None, it is removed from the dict. If element + is not a dict, or if the identifier points to something that is + not, a DataTypeError is raised. The element is updated inline, + so if the original needs to be kept, you must make a copy before + calling set(). The updated base element is returned (so that + el.set().set().set() is possible)""" + if type(element) != dict: + raise DataTypeError("element in set() is not a dict") + if type(identifier) != str: + raise DataTypeError("identifier in set() is not a string") id_parts = identifier.split("/") id_parts[:] = (value for value in id_parts if value != "") cur_el = element @@ -41,36 +78,46 @@ def set(element, identifier, value): if id in cur_el.keys(): cur_el = cur_el[id] else: - cur_el[id] = {} - cur_el = cur_el[id] - cur_el[id_parts[-1]] = value + if value: + cur_el[id] = {} + cur_el = cur_el[id] + else: + # set to none, and parent el not found, return + return element + if value: + cur_el[id_parts[-1]] = value + else: + del cur_el[id_parts[-1]] return element def unset(element, identifier): - id_parts = identifier.split("/") - id_parts[:] = (value for value in id_parts if value != "") - cur_el = element - for id in id_parts[:-1]: - if id in cur_el.keys(): - cur_el = cur_el[id] - else: - cur_el[id] = {} - cur_el = cur_el[id] - cur_el[id_parts[-1]] = None - return element + """Removes the element at the given identifier if it exists. Raises + a DataTypeError if element is not a dict or if identifier is not + a string. Returns the base element.""" + # perhaps we can simply do with set none, and remove this whole + # function + return set(element, identifier, None) def find_no_exc(element, identifier): - """Returns the subelement in the given data element, returns None if not found""" + """Returns the subelement in the given data element, returns None + if not found, or if an error occurred (i.e. this function should + never raise an exception)""" + if type(identifier) != str: + return None id_parts = identifier.split("/") id_parts[:] = (value for value in id_parts if value != "") cur_el = element for id in id_parts: - if type(cur_el) == dict and id in cur_el.keys(): + if (type(cur_el) == dict and id in cur_el.keys()) or id=="": cur_el = cur_el[id] else: return None return cur_el +# +# hmm, these are more relevant for datadefition +# should we (re)move them? +# def find_spec(element, identifier): """find the data definition for the given identifier returns either a map with 'item_name' etc, or a list of those""" @@ -147,6 +194,11 @@ def spec_name_list(spec, prefix="", recurse=False): return result def parse_value_str(value_str): + """Parses the given string to a native python object. If the + string cannot be parsed, it is returned. If it is not a string, + None is returned""" + if type(value_str) != str: + return None try: return ast.literal_eval(value_str) except ValueError as ve: diff --git a/src/lib/cc/python/isc/cc/data_test.py b/src/lib/cc/python/isc/cc/data_test.py new file mode 100644 index 0000000000..0c6e8bc9b4 --- /dev/null +++ b/src/lib/cc/python/isc/cc/data_test.py @@ -0,0 +1,106 @@ +# Copyright (C) 2010 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. + +# +# Tests for the functions in data.py +# + +import unittest +import os +import data + +class TestData(unittest.TestCase): + def test_merge(self): + d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } } + d2 = { 'a': None, 'c': { 'd': None, 'e': 3, 'f': [ 1 ] } } + d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } } + m12 = d1 + data.merge(m12, d2) + self.assertEqual(d12, m12) + self.assertRaises(data.DataTypeError, data.merge, d1, "a") + self.assertRaises(data.DataTypeError, data.merge, 1, d2) + self.assertRaises(data.DataTypeError, data.merge, None, None) + + def test_find(self): + d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } } + self.assertEqual(data.find(d1, ''), d1) + self.assertEqual(data.find(d1, 'a'), 'a') + self.assertEqual(data.find(d1, 'c/e'), 2) + self.assertEqual(data.find(d1, 'c/more/'), { 'data': 'here' }) + self.assertEqual(data.find(d1, 'c/more/data'), 'here') + self.assertRaises(data.DataNotFoundError, data.find, d1, 'c/f') + self.assertRaises(data.DataNotFoundError, data.find, d1, 'f') + self.assertRaises(data.DataTypeError, data.find, d1, 1) + self.assertRaises(data.DataTypeError, data.find, None, 1) + self.assertRaises(data.DataTypeError, data.find, "123", "123") + self.assertEqual(data.find("123", ""), "123") + + def test_set(self): + d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } } + d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } } + data.set(d1, 'a', None) + data.set(d1, 'c/d', None) + data.set(d1, 'c/e/', 3) + data.set(d1, 'c/f', [ 1 ] ) + self.assertEqual(d1, d12) + self.assertRaises(data.DataTypeError, data.set, d1, 1, 2) + self.assertRaises(data.DataTypeError, data.set, 1, "", 2) + d3 = {} + e3 = data.set(d3, "does/not/exist", 123) + self.assertEqual(d3, + { 'does': { 'not': { 'exist': 123 } } }) + self.assertEqual(e3, + { 'does': { 'not': { 'exist': 123 } } }) + + def test_unset(self): + d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } } + data.unset(d1, 'a') + data.unset(d1, 'c/d') + data.unset(d1, 'does/not/exist') + self.assertEqual(d1, { 'b': 1, 'c': { 'e': 2 } }) + + def test_find_no_exc(self): + d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } } + self.assertEqual(data.find_no_exc(d1, ''), d1) + self.assertEqual(data.find_no_exc(d1, 'a'), 'a') + self.assertEqual(data.find_no_exc(d1, 'c/e'), 2) + self.assertEqual(data.find_no_exc(d1, 'c/more/'), { 'data': 'here' }) + self.assertEqual(data.find_no_exc(d1, 'c/more/data'), 'here') + self.assertEqual(data.find_no_exc(d1, 'c/f'), None) + self.assertEqual(data.find_no_exc(d1, 'f'), None) + self.assertEqual(data.find_no_exc(d1, 1), None) + self.assertEqual(data.find_no_exc(d1, 'more/data/here'), None) + self.assertEqual(data.find_no_exc(None, 1), None) + self.assertEqual(data.find_no_exc("123", ""), "123") + self.assertEqual(data.find_no_exc("123", ""), "123") + + def test_parse_value_str(self): + self.assertEqual(data.parse_value_str("1"), 1) + self.assertEqual(data.parse_value_str("True"), True) + self.assertEqual(data.parse_value_str("None"), None) + self.assertEqual(data.parse_value_str("1.1"), 1.1) + self.assertEqual(data.parse_value_str("[]"), []) + self.assertEqual(data.parse_value_str("[ 1, None, 'asdf' ]"), [ 1, None, "asdf" ]) + self.assertEqual(data.parse_value_str("{}"), {}) + self.assertEqual(data.parse_value_str("{ 'a': 'b', 'c': 1 }"), { 'a': 'b', 'c': 1 }) + self.assertEqual(data.parse_value_str("[ a c"), "[ a c") + +if __name__ == '__main__': + #if not 'CONFIG_TESTDATA_PATH' in os.environ: + # print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files") + # exit(1) + unittest.main() + + diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index e14bd29268..0fa1af9cd8 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -1,3 +1,22 @@ +# Copyright (C) 2010 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. + +# +# This is the main class for the b10-cfgmgr daemon +# + import isc import signal import ast @@ -122,7 +141,11 @@ class ConfigManager: def read_config(self): """Read the current configuration from the b10-config.db file at the path specificied at init()""" - self.config = ConfigManagerData.read_from_file(self.data_path) + try: + self.config = ConfigManagerData.read_from_file(self.data_path) + except ConfigManagerDataEmpty: + # ok, just start with an empty config + self.config = ConfigManagerData(self.data_path) def write_config(self): """Write the current configuration to the b10-config.db file diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 60c6214fbf..f566a566ad 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 Internet Systems Consortium. +# Copyright (C) 2010 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 From bde6077141fdd9ef3c13113dd9697b5f88c2f5d2 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 10 Feb 2010 03:52:29 +0000 Subject: [PATCH 06/81] fixed an obvious error in assignment (fixed in trunk, this will get reviewed in the next cycle of branching) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@775 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rdata/generic/rrsig_46.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dns/cpp/rdata/generic/rrsig_46.cc b/src/lib/dns/cpp/rdata/generic/rrsig_46.cc index 8c211cac43..8952ccab23 100644 --- a/src/lib/dns/cpp/rdata/generic/rrsig_46.cc +++ b/src/lib/dns/cpp/rdata/generic/rrsig_46.cc @@ -124,7 +124,7 @@ RRSIG::operator=(const RRSIG& source) return (*this); } - RRSIGImpl* newimpl = new RRSIGImpl(*impl_); + RRSIGImpl* newimpl = new RRSIGImpl(*source.impl_); delete impl_; impl_ = newimpl; From fadf58e7eebdad9576ae60852583012e9db57932 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 10 Feb 2010 19:05:57 +0000 Subject: [PATCH 07/81] Remove parkinglot from trunk. It is still in branches/parkinglot if needed. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@784 e5f2f494-b856-4b98-b285-d166d9295462 --- configure.ac | 2 - src/bin/Makefile.am | 2 +- src/bin/parkinglot/Makefile.am | 24 --- src/bin/parkinglot/b10-parkinglot.cc | 241 ------------------------- src/bin/parkinglot/b10-parkinglot.h | 44 ----- src/bin/parkinglot/builtin.cc | 217 ---------------------- src/bin/parkinglot/builtin.h | 49 ----- src/bin/parkinglot/builtin_bench.cc | 241 ------------------------- src/bin/parkinglot/common.cc | 23 --- src/bin/parkinglot/common.h | 36 ---- src/bin/parkinglot/config.h.in | 1 - src/bin/parkinglot/data_source.cc | 20 -- src/bin/parkinglot/data_source.h | 123 ------------- src/bin/parkinglot/data_source_plot.cc | 188 ------------------- src/bin/parkinglot/data_source_plot.h | 96 ---------- src/bin/parkinglot/main.cc | 148 --------------- src/bin/parkinglot/parkinglot.spec | 74 -------- src/bin/parkinglot/zoneset.cc | 37 ---- src/bin/parkinglot/zoneset.h | 64 ------- src/lib/cc/cpp/parkinglot.data | 12 -- src/lib/cc/cpp/parkinglot.spec | 46 ----- 21 files changed, 1 insertion(+), 1687 deletions(-) delete mode 100644 src/bin/parkinglot/Makefile.am delete mode 100644 src/bin/parkinglot/b10-parkinglot.cc delete mode 100644 src/bin/parkinglot/b10-parkinglot.h delete mode 100644 src/bin/parkinglot/builtin.cc delete mode 100644 src/bin/parkinglot/builtin.h delete mode 100644 src/bin/parkinglot/builtin_bench.cc delete mode 100644 src/bin/parkinglot/common.cc delete mode 100644 src/bin/parkinglot/common.h delete mode 100644 src/bin/parkinglot/config.h.in delete mode 100644 src/bin/parkinglot/data_source.cc delete mode 100644 src/bin/parkinglot/data_source.h delete mode 100644 src/bin/parkinglot/data_source_plot.cc delete mode 100644 src/bin/parkinglot/data_source_plot.h delete mode 100644 src/bin/parkinglot/main.cc delete mode 100644 src/bin/parkinglot/parkinglot.spec delete mode 100644 src/bin/parkinglot/zoneset.cc delete mode 100644 src/bin/parkinglot/zoneset.h delete mode 100644 src/lib/cc/cpp/parkinglot.data delete mode 100644 src/lib/cc/cpp/parkinglot.spec diff --git a/configure.ac b/configure.ac index 2f2a91a69c..0d8e11ce3b 100644 --- a/configure.ac +++ b/configure.ac @@ -148,7 +148,6 @@ AC_CONFIG_FILES([Makefile src/bin/host/Makefile src/bin/msgq/Makefile src/bin/auth/Makefile - src/bin/parkinglot/Makefile src/lib/Makefile src/lib/cc/Makefile src/lib/cc/cpp/Makefile @@ -181,7 +180,6 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py src/bin/msgq/msgq_test src/bin/msgq/run_msgq.sh src/bin/auth/config.h - src/bin/parkinglot/config.h src/lib/config/cpp/data_def_unittests_config.h src/lib/config/python/isc/config/config_test src/lib/dns/cpp/gen-rdatacode.py diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 4cb065ee58..a71a81d53d 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -1 +1 @@ -SUBDIRS = bind10 bindctl cfgmgr msgq host cmdctl auth parkinglot +SUBDIRS = bind10 bindctl cfgmgr msgq host cmdctl auth diff --git a/src/bin/parkinglot/Makefile.am b/src/bin/parkinglot/Makefile.am deleted file mode 100644 index d110c1539d..0000000000 --- a/src/bin/parkinglot/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_builddir)/src/lib -I$(top_builddir)/src/lib/dns/cpp -I$(top_builddir)/include/dns/cpp -I$(top_builddir)/include -I$(top_srcdir)/ext - -pkglibexecdir = $(libexecdir)/@PACKAGE@ - -pkglibexec_PROGRAMS = b10-parkinglot - -b10_parkinglot_SOURCES = b10-parkinglot.cc b10-parkinglot.h -b10_parkinglot_SOURCES += common.cc common.h zoneset.h zoneset.cc main.cc -b10_parkinglot_SOURCES += data_source_plot.h data_source_plot.cc -b10_parkinglot_SOURCES += data_source.h data_source.cc -b10_parkinglot_SOURCES += builtin.h builtin.cc -b10_parkinglot_LDADD = $(top_builddir)/src/lib/dns/cpp/.libs/libdns.a -b10_parkinglot_LDADD += $(top_builddir)/src/lib/config/cpp/libcfgclient.a -b10_parkinglot_LDADD += $(top_builddir)/src/lib/cc/cpp/libcc.a -b10_parkinglot_LDADD += $(top_builddir)/src/lib/exceptions/cpp/.libs/libexceptions.a - -b10_parkinglotdir = $(DESTDIR)$(pkgdatadir) -b10_parkinglot_DATA = parkinglot.spec - -# TODO: don't install this here -bin_PROGRAMS = builtin_bench -builtin_bench_SOURCES = builtin_bench.cc builtin.h builtin.cc -builtin_bench_LDADD = $(top_builddir)/src/lib/dns/cpp/.libs/libdns.a -builtin_bench_LDADD += $(top_builddir)/src/lib/exceptions/cpp/.libs/libexceptions.a diff --git a/src/bin/parkinglot/b10-parkinglot.cc b/src/bin/parkinglot/b10-parkinglot.cc deleted file mode 100644 index f45e377e1a..0000000000 --- a/src/bin/parkinglot/b10-parkinglot.cc +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "common.h" -#include "builtin.h" -#include "b10-parkinglot.h" - -#include -#include - -using namespace std; - -using namespace isc::dns; -using namespace isc::dns::rdata; -using namespace isc::data; - -ParkingLot::ParkingLot(int port) { - /*ns1 = Rdata::RdataPtr(new NS("ns1.parking.example")); - ns2 = Rdata::RdataPtr(new NS("ns2.parking.example")); - ns3 = Rdata::RdataPtr(new NS("ns3.parking.example")); - a = Rdata::RdataPtr(new A("127.0.0.1")); - aaaa = Rdata::RdataPtr(new AAAA("::1")); - */ - - int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (s < 0) - throw FatalError("failed to open socket"); - - struct sockaddr_in sin; - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(port); - - socklen_t sa_len = sizeof(sin); -#ifdef HAVE_SIN_LEN - sin.sin_len = sa_len; -#endif - - if (bind(s, (struct sockaddr *)&sin, sa_len) < 0) - throw FatalError("could not bind socket"); - - sock = s; -} - -namespace { -struct GlueInserter { - GlueInserter(const DataSourceParkingLot& data_source, - const Name& zname, const RRClass& qclass, - Message& msg) : - data_source_(&data_source), zname_(&zname), qclass_(&qclass), - msg_(&msg) - {} - void operator()(const RRsetPtr rrset) - { - if (rrset->getType() == RRType::NS()) { - RdataIteratorPtr it = rrset->getRdataIterator(); - for (it->first(); !it->isLast(); it->next()) { - const generic::NS& nsrdata = - dynamic_cast(it->getCurrent()); - data_source_->addToMessage(*msg_, Section::ADDITIONAL(), - *zname_, nsrdata.getNSName(), - *qclass_, RRType::A()); - data_source_->addToMessage(*msg_, Section::ADDITIONAL(), - *zname_, nsrdata.getNSName(), - *qclass_, RRType::AAAA()); - } - } - } - const DataSourceParkingLot* data_source_; - const Name* zname_; - const RRClass* qclass_; - Message* msg_; -}; -} - -namespace { -const Name authors_name("authors.bind"); -const Name version_name("version.bind"); -} - -void -ParkingLot::processMessage() { - struct sockaddr_storage ss; - socklen_t sa_len = sizeof(ss); - struct sockaddr* sa = static_cast((void*)&ss); - int s = sock; - char recvbuf[4096]; - int cc; - - if ((cc = recvfrom(s, recvbuf, sizeof(recvbuf), 0, sa, &sa_len)) > 0) { - Message msg; - InputBuffer buffer(recvbuf, cc); - - try { - msg.fromWire(buffer); - } catch (...) { - cerr << "parse failed" << endl; - return; - } - - cout << "received a message:\n" << msg.toText() << endl; - - if (msg.getRRCount(Section::QUESTION()) != 1) { - return; - } - - QuestionPtr query = *msg.beginQuestion(); - - msg.makeResponse(); - msg.setHeaderFlag(MessageFlag::AA()); - Name zname("."); // ugly, but should work for now - msg.setRcode(Rcode::NOERROR()); - if (query->getType() == RRType::TXT() && - query->getClass() == RRClass::CH() && - query->getName() == authors_name) { - msg.addRRset(Section::ANSWER(), getBuiltinAuthors().getAnswer()); - msg.addRRset(Section::AUTHORITY(), - getBuiltinAuthors().getAuthority()); - } else if (query->getType() == RRType::TXT() && - query->getClass() == RRClass::CH() && - query->getName() == version_name) { - msg.addRRset(Section::ANSWER(), getBuiltinVersion().getAnswer()); - msg.addRRset(Section::AUTHORITY(), - getBuiltinVersion().getAuthority()); - } else if (data_source.hasZoneFor(query->getName(), zname)) { - SearchResult::status_type status = - data_source.addToMessage(msg, Section::ANSWER(), zname, - query->getName(), query->getClass(), - query->getType()); - bool included_ns = false; - - // rcode is based on this result? - if (status == SearchResult::name_not_found) { - msg.setRcode(Rcode::NXDOMAIN()); - if (query->getType() != RRType::NS()) { - status = data_source.addToMessage(msg, Section::AUTHORITY(), - zname, zname, - query->getClass(), - RRType::SOA()); - } - } else { - if (query->getType() != RRType::NS()) { - status = data_source.addToMessage(msg, Section::AUTHORITY(), - zname, zname, - query->getClass(), - RRType::NS()); - } - included_ns = true; - } - // If we included NS records, and their target falls below the zone, add glue - if (included_ns) { - for_each(msg.beginSection(Section::ANSWER()), - msg.endSection(Section::ANSWER()), - GlueInserter(data_source, zname, query->getClass(), - msg)); - for_each(msg.beginSection(Section::AUTHORITY()), - msg.endSection(Section::AUTHORITY()), - GlueInserter(data_source, zname, query->getClass(), - msg)); - } - } else { - msg.setRcode(Rcode::SERVFAIL()); - } - - OutputBuffer obuffer(4096); - MessageRenderer renderer(obuffer); - msg.toWire(renderer); - cout << "sending a response (" << - boost::lexical_cast(obuffer.getLength()) - << " bytes):\n" << msg.toText() << endl; - sendto(s, obuffer.getData(), obuffer.getLength(), 0, sa, sa_len); - } -} - -ElementPtr -ParkingLot::updateConfig(isc::data::ElementPtr config) { - if (config->contains("zones")) { - data_source.clear_zones(); - BOOST_FOREACH(isc::data::ElementPtr zone_el, config->get("zones")->listValue()) { - data_source.serve(zone_el->stringValue()); - } - } - if (config->contains("port")) { - // todo: what to do with port change. restart automatically? - // ignore atm - } - if (config->contains("a_records")) { - data_source.clearARecords(); - BOOST_FOREACH(isc::data::ElementPtr rel, config->get("a_records")->listValue()) { - data_source.addARecord(rel->stringValue()); - } - } - if (config->contains("aaaa_records")) { - data_source.clearAAAARecords(); - BOOST_FOREACH(isc::data::ElementPtr rel, config->get("aaaa_records")->listValue()) { - data_source.addAAAARecord(rel->stringValue()); - } - } - if (config->contains("ns_records")) { - data_source.clearNSRecords(); - BOOST_FOREACH(isc::data::ElementPtr rel, config->get("ns_records")->listValue()) { - data_source.addNSRecord(rel->stringValue()); - } - } - return isc::data::Element::createFromString("{ \"result\": [0] }"); -} diff --git a/src/bin/parkinglot/b10-parkinglot.h b/src/bin/parkinglot/b10-parkinglot.h deleted file mode 100644 index 2124ea7311..0000000000 --- a/src/bin/parkinglot/b10-parkinglot.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __PARKINGLOT_H -#define __PARKINGLOT_H 1 - -#include "zoneset.h" -#include -#include "data_source_plot.h" - -class ParkingLot { -public: - explicit ParkingLot(int port); - virtual ~ParkingLot() {} - int getSocket() { return (sock); } - void processMessage(); - void command(std::pair); - void serve(std::string zone_name); - - isc::data::ElementPtr updateConfig(isc::data::ElementPtr config); -private: - - isc::dns::DataSourceParkingLot data_source; - int sock; -}; - -#endif // __PARKINGLOT_H - -// Local Variables: -// mode: c++ -// End: diff --git a/src/bin/parkinglot/builtin.cc b/src/bin/parkinglot/builtin.cc deleted file mode 100644 index 2fb6f91df0..0000000000 --- a/src/bin/parkinglot/builtin.cc +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "builtin.h" - -using namespace std; -using namespace isc::dns; -using namespace isc::dns::rdata; - -namespace { -const Name authors_name("authors.bind"); -const Name version_name("version.bind"); - -const char* authors[] = { - "Han Feng", - "Kazunori Fujiwara", - "Michael Graff", - "Evan Hunt", - "Jelte Jansen", - "Jin Jian", - "JINMEI Tatuya", - "Naoki Kambe", - "Shane Kerr", - "Zhang Likun", - "Jeremy C. Reed", - NULL -}; - -const char* version[] = { - "BIND10 0.0.1", - NULL -}; - -const char* author_authorities[] = { - "authors.bind", - NULL -}; - -const char* version_authorities[] = { - "version.bind", - NULL -}; - -class BuiltinRRset : public AbstractRRset { -private: - BuiltinRRset(const BuiltinRRset& source); - void operator=(const BuiltinRRset& source); -public: - BuiltinRRset(const Name& name, const RRType& rrtype, - const char** rdata_string_list); - virtual void addRdata(const rdata::RdataPtr rdata) - { - // ignore it (or we might throw an exception) - } - virtual unsigned int getRdataCount() const { return (num_rdata_); } - virtual const Name& getName() const { return (name_); } - virtual const RRClass& getClass() const { return (rrclass_); } - virtual const RRType& getType() const { return (rrtype_); } - virtual const RRTTL& getTTL() const { return (rrttl_); } - virtual void setTTL(const RRTTL& ttl) - { - // ignore it (or we might throw an exception) - } - virtual unsigned int toWire(MessageRenderer& renderer) const; - virtual RdataIteratorPtr getRdataIterator() const; - - const Name name_; - const RRType rrtype_; - const RRClass rrclass_; - const RRTTL rrttl_; - vector rdatavector_; - unsigned int num_rdata_; - OutputBuffer wire_data_; // pre-rendered RRset - size_t data_offset_; // offset to the RRset data - - static const size_t DATA_OFFSET = 12; // length of DNS header -}; - -class BuiltinRdataIterator : public RdataIterator { -private: - BuiltinRdataIterator() {} -public: - BuiltinRdataIterator(const vector& datavector) : - datavector_(&datavector) {} - ~BuiltinRdataIterator() {} - virtual void first() { it_ = datavector_->begin(); } - virtual void next() { ++it_; } - virtual const rdata::Rdata& getCurrent() const { return (**it_); } - virtual bool isLast() const { return (it_ == datavector_->end()); } -private: - const vector* datavector_; - vector::const_iterator it_; -}; -} - -BuiltinRRset::BuiltinRRset(const Name& name, const RRType& rrtype, - const char** rdata_string_list) : - name_(name), rrtype_(rrtype), rrclass_(RRClass::CH()), rrttl_(RRTTL(0)), - num_rdata_(0), wire_data_(512) -{ - MessageRenderer renderer(wire_data_); - renderer.skip(DATA_OFFSET); // leave the space for the DNS header - renderer.writeName(name); - data_offset_ = renderer.getLength(); // remember the start position - - RRset tmprrset(name, rrclass_, rrtype_, rrttl_); - - for (int i = 0; rdata_string_list[i] != NULL; ++i) { - RdataPtr rdata = createRdata(rrtype_, rrclass_, - string(rdata_string_list[i])); - rdatavector_.push_back(rdata); - tmprrset.addRdata(rdata); - } - - // pre-render the RRset - tmprrset.toWire(renderer); - num_rdata_ = rdatavector_.size(); -} - -unsigned int -BuiltinRRset::toWire(MessageRenderer& renderer) const -{ - // XXX: we should confirm the query name matches the pre-rendered data - // and it's stored in the Question section. This proof-of-concept - // implementation omits the check for brevity. - renderer.writeData(static_cast(wire_data_.getData()) - + data_offset_, wire_data_.getLength() - data_offset_); - return (num_rdata_); -} - -RdataIteratorPtr -BuiltinRRset::getRdataIterator() const -{ - return (RdataIteratorPtr(new BuiltinRdataIterator(rdatavector_))); -} - -struct BuiltinRRsetsImpl { - BuiltinRRsetsImpl(const Name& answer_name, const char** answers, - const char** authorities); - RRsetPtr rrset_answer; - RRsetPtr rrset_authority; -}; - -BuiltinRRsetsImpl::BuiltinRRsetsImpl(const Name& answer_name, - const char** answers, - const char** authorities) -{ - rrset_answer = RRsetPtr(new BuiltinRRset(answer_name, RRType::TXT(), - answers)); - rrset_authority = RRsetPtr(new BuiltinRRset(answer_name, RRType::NS(), - authorities)); -} - -BuiltinRRsets::BuiltinRRsets(const Name& name, - const char** answers, - const char** authorities) -{ - impl_ = new BuiltinRRsetsImpl(name, answers, authorities); -} - -BuiltinRRsets::~BuiltinRRsets() -{ - delete impl_; -} - -RRsetPtr -BuiltinRRsets::getAnswer() const -{ - return (impl_->rrset_answer); -} - -RRsetPtr -BuiltinRRsets::getAuthority() const -{ - return (impl_->rrset_authority); -} - -const BuiltinRRsets& -getBuiltinAuthors() -{ - static BuiltinRRsets builtin_authors(authors_name, authors, - author_authorities); - return (builtin_authors); -} - -const BuiltinRRsets& -getBuiltinVersion() -{ - static BuiltinRRsets builtin_version(version_name, version, - version_authorities); - return (builtin_version); -} diff --git a/src/bin/parkinglot/builtin.h b/src/bin/parkinglot/builtin.h deleted file mode 100644 index d20b18fd7f..0000000000 --- a/src/bin/parkinglot/builtin.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __BUILTIN_H -#define __BUILTIN_H 1 - -#include - -#include -#include - -struct BuiltinRRsetsImpl; - -class isc::dns::Name; - -class BuiltinRRsets { -public: - BuiltinRRsets(const isc::dns::Name& name, const char** answers, - const char** authorities); - ~BuiltinRRsets(); - isc::dns::RRsetPtr getAnswer() const; - isc::dns::RRsetPtr getAuthority() const; -private: - BuiltinRRsets(const BuiltinRRsets& source); - void operator=(const BuiltinRRsets& source); - BuiltinRRsetsImpl* impl_; -}; - -const BuiltinRRsets& getBuiltinAuthors(); -const BuiltinRRsets& getBuiltinVersion(); - -#endif // __BUILTIN_H - -// Local Variables: -// mode: c++ -// End: diff --git a/src/bin/parkinglot/builtin_bench.cc b/src/bin/parkinglot/builtin_bench.cc deleted file mode 100644 index ad4d319e47..0000000000 --- a/src/bin/parkinglot/builtin_bench.cc +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "builtin.h" - -using namespace std; -using namespace isc::dns; -using namespace isc::dns::rdata; - -static const int ITERATION = 100000; - -namespace { -Name authors_name("authors.bind"); - -inline double -tv_sub(const struct timeval& t1, const struct timeval& t2) -{ - struct timeval result; - - result.tv_sec = t1.tv_sec - t2.tv_sec; - result.tv_usec = t1.tv_usec - t2.tv_usec; - if (result.tv_usec < 0) { - result.tv_usec += 1000000; - result.tv_sec--; - } - - return (result.tv_sec + result.tv_usec / 1000000.0); -} - -void -buildAuthors(vector& authors) -{ - authors.push_back("Han Feng"); - authors.push_back("Kazunori Fujiwara"); - authors.push_back("Michael Graff"); - authors.push_back("Evan Hunt"); - authors.push_back("Jelte Jansen"); - authors.push_back("Jin Jian"); - authors.push_back("JINMEI Tatuya"); - authors.push_back("Naoki Kambe"); - authors.push_back("Shane Kerr"); - authors.push_back("Zhang Likun"); - authors.push_back("Jeremy C. Reed"); -} - -struct RdataInserterFromText -{ - RdataInserterFromText(RRsetPtr rrset) : rrset_(rrset) {} - void operator()(const string& author) - { - rrset_->addRdata(generic::TXT(author)); - } - RRsetPtr rrset_; -}; - -inline RRsetPtr -getNormalRRset(const vector& authors) -{ - RRsetPtr rrset = RRsetPtr(new RRset(authors_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - for_each(authors.begin(), authors.end(), RdataInserterFromText(rrset)); - - return (rrset); -} - -struct RdataInserterFromWire -{ - RdataInserterFromWire(RRsetPtr rrset) : rrset_(rrset) {} - void operator()(const OutputBuffer& buffer) - { - InputBuffer b(buffer.getData(), buffer.getLength()); - rrset_->addRdata(RdataPtr(new generic::TXT(b, buffer.getLength()))); - } - RRsetPtr rrset_; -}; - -inline RRsetPtr -getNormalRRset(const vector& buffers) -{ - RRsetPtr rrset = RRsetPtr(new RRset(authors_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - for_each(buffers.begin(), buffers.end(), RdataInserterFromWire(rrset)); - - return (rrset); -} - -struct WireDataInserter -{ - WireDataInserter(vector& buffers) : buffers_(buffers) - {} - void operator()(const string& author) - { - OutputBuffer buffer(0); - generic::TXT(author).toWire(buffer); - buffers_.push_back(buffer); - } - vector& buffers_; -}; - -void -showResult(const struct timeval& tv_begin, const struct timeval& tv_end, - int iteration, const char* description) -{ - double diff = tv_sub(tv_end, tv_begin); - cout << description << " " << iteration << " times in " - << fixed << diff << "sec (" - << static_cast(iteration / diff) << "qps)" << endl; -} -} - -int -main(int argc, char* argv[]) -{ - int iteration = ITERATION; - if (argc > 1) { - istringstream(argv[1]) >> dec >> iteration; - } - - vector authors; - buildAuthors(authors); - - struct timeval tv_begin, tv_end; - OutputBuffer buffer(512); - MessageRenderer renderer(buffer); - - // - // Benchmark for rendering an optimized pre-format RRset - // - gettimeofday(&tv_begin, NULL); - for (int i = 0; i < iteration; ++i) { - renderer.clear(); - renderer.skip(12); - renderer.writeName(authors_name); - RRsetPtr rrset_optimized = getBuiltinAuthors().getAnswer(); - rrset_optimized->toWire(renderer); - } - gettimeofday(&tv_end, NULL); - showResult(tv_begin, tv_end, iteration, "Rendered optimized RRset"); - - vector data(buffer.getLength()); - memcpy(&data[0], buffer.getData(), buffer.getLength()); - - // - // Benchmark for rendering a general purpose RRset of the same content - // - RRsetPtr rrset_normal = getNormalRRset(authors); - gettimeofday(&tv_begin, NULL); - for (int i = 0; i < iteration; ++i) { - renderer.clear(); - renderer.skip(12); - renderer.writeName(authors_name); - rrset_normal->toWire(renderer); - } - gettimeofday(&tv_end, NULL); - showResult(tv_begin, tv_end, iteration, "Rendered normal RRset"); - - // Confirm the two sets of output are identical. - if (data.size() != buffer.getLength() || - memcmp(&data[0], buffer.getData(), buffer.getLength())) { - cerr << "Rendered data mismatch" << endl; - return (1); - } - - // - // Benchmark for rendering a general purpose RRset of the same content, - // create RRset from text every time - // - gettimeofday(&tv_begin, NULL); - for (int i = 0; i < iteration; ++i) { - renderer.clear(); - renderer.skip(12); - renderer.writeName(authors_name); - getNormalRRset(authors)->toWire(renderer); - } - gettimeofday(&tv_end, NULL); - showResult(tv_begin, tv_end, iteration, - "Rendered normal RRset with fromText"); - - // Confirm the two sets of output are identical. - if (data.size() != buffer.getLength() || - memcmp(&data[0], buffer.getData(), buffer.getLength())) { - cerr << "Rendered data mismatch" << endl; - return (1); - } - - // - // Benchmark for rendering a general purpose RRset of the same content, - // create RRset from wire data every time - // - vector buffers; - for_each(authors.begin(), authors.end(), WireDataInserter(buffers)); - gettimeofday(&tv_begin, NULL); - for (int i = 0; i < iteration; ++i) { - renderer.clear(); - renderer.skip(12); - renderer.writeName(authors_name); - getNormalRRset(buffers)->toWire(renderer); - } - gettimeofday(&tv_end, NULL); - showResult(tv_begin, tv_end, iteration, - "Rendered normal RRset with fromWire"); - - // Confirm the two sets of output are identical. - if (data.size() != buffer.getLength() || - memcmp(&data[0], buffer.getData(), buffer.getLength())) { - cerr << "Rendered data mismatch" << endl; - return (1); - } - - return (0); -} diff --git a/src/bin/parkinglot/common.cc b/src/bin/parkinglot/common.cc deleted file mode 100644 index 2c01050b07..0000000000 --- a/src/bin/parkinglot/common.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ -#include "common.h" -#include - -FatalError::FatalError(std::string m) { - msg = m; - std::cerr << msg << std::endl; - exit(1); -} diff --git a/src/bin/parkinglot/common.h b/src/bin/parkinglot/common.h deleted file mode 100644 index 6345093e99..0000000000 --- a/src/bin/parkinglot/common.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __COMMON_H -#define __COMMON_H 1 - -#include -#include - -class FatalError : public std::exception { -public: - FatalError(std::string m = "fatal error"); - ~FatalError() throw() {} - const char* what() const throw() { return msg.c_str(); } -private: - std::string msg; -}; - -#endif // __COMMON_H - -// Local Variables: -// mode: c++ -// End: diff --git a/src/bin/parkinglot/config.h.in b/src/bin/parkinglot/config.h.in deleted file mode 100644 index a2ed177793..0000000000 --- a/src/bin/parkinglot/config.h.in +++ /dev/null @@ -1 +0,0 @@ -#define PARKINGLOT_SPECFILE_LOCATION "@prefix@/share/bind/parkinglot.spec" diff --git a/src/bin/parkinglot/data_source.cc b/src/bin/parkinglot/data_source.cc deleted file mode 100644 index fd07fb332b..0000000000 --- a/src/bin/parkinglot/data_source.cc +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "data_source.h" - -namespace isc { -namespace dns { - -void -DataSource::getData(isc::dns::RRsetPtr query, isc::dns::Message& answer) -{ - return; -} - -} -} diff --git a/src/bin/parkinglot/data_source.h b/src/bin/parkinglot/data_source.h deleted file mode 100644 index 0c69debb61..0000000000 --- a/src/bin/parkinglot/data_source.h +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __DATA_SOURCE_H -#define __DATA_SOURCE_H - -#include -#include - -namespace isc { -namespace dns { - -// do we need to make a template for this? -// i.e. do we want this to be able to hold more types than RRset? -class SearchResult { -public: - SearchResult() : status(success) {}; - enum status_type { success, error, not_implemented, - zone_not_found, name_not_found }; - - void addRRset(RRsetPtr rrset) { rrsets.push_back(rrset); } - - status_type getStatus() { return status; } - void setStatus(status_type s) { status = s; } - - /* forward iterator interface */ - typedef std::vector::iterator iterator; - typedef std::vector::const_iterator const_iterator; - std::vector::iterator begin() { return rrsets.begin(); } - std::vector::iterator end() { return rrsets.end(); } - std::vector::const_iterator begin() const { return rrsets.begin(); } - std::vector::const_iterator end() const { return rrsets.end(); } - -private: - status_type status; - std::vector rrsets; -}; - -// Base class for a DNS Data Source -class DataSource { -public: - //enum result { success, not_implemented, error, zone_not_found, - // name_not_found }; - - DataSource() {}; - virtual ~DataSource() {}; - - // - // 'high-level' methods, these may or may not be overwritten; - // depending on the data source the default implementation - // may not be most efficient, but should at least provide - // the needed functionality - // - - // fill in separate lists or simple fill in an answer Message object? - void getData(const RRsetPtr query, Message& answer); - - // how to provide highlevel update data? - //result handleUpdate() - - - // - // mandatory 'low-level' methods, an implementation must overwrite these - // - // - // for a 'catch-all' datasource, we need to be able to find - // out if it has a zone for a given name - // - // perhaps this should not set a zone Name, but rather a specific - // ZoneRef option (which could be datasource-specific, as it will - // only be used to pass along to other calls) - // Or even more abstract; - // SomeHandler initFind(name, whatever else); - virtual bool hasZoneFor(const Name& name, Name& zone_name) = 0; - - // for the zone_name, see getZoneFor, perhaps this needs to be a more - // general handle - // And perhaps we need a function that does not have that argument too - virtual SearchResult findRRsets(const Name& zone_name, - const Name& name, - const RRClass& clas, - const RRType& type) const = 0; - - // - // optional 'low-level' methods, an implementation may overwrite these, - // by default they return not_implemented - // - virtual void init() {}; - virtual void close() {}; - //virtual result addRR(Name name, int clas, int type, - // int ttl, Rdata::RdataPtr data) - // { return not_implemented; }; - //virtual result delRR(isc::dns::Name name, int clas, int type) = 0; - // on name/class/type again? or use an already constructed object? - //virtual result getRRSigs(RRsetPtr target, const RRsetPtr rrset) - // { return not_implemented; }; - //virtual result getNSECs(RRsetPtr target, const RRsetPtr rrset) - // { return not_implemented; }; - - // check if the zone exists, and if so, return something that could - // be used as a pointer for the rest of these functions? - // do we need this? do we want this? - //virtual int getZone(void* zone, isc::dns::Name name); - -}; - -} -} - -#endif diff --git a/src/bin/parkinglot/data_source_plot.cc b/src/bin/parkinglot/data_source_plot.cc deleted file mode 100644 index 0543ea0f33..0000000000 --- a/src/bin/parkinglot/data_source_plot.cc +++ /dev/null @@ -1,188 +0,0 @@ - -#include - -#include -#include - -#include "data_source_plot.h" - -namespace isc { -namespace dns { - -// this implementation returns fixed records, -// and does not allow update statements - -using namespace isc::dns; -using namespace isc::dns::rdata; -using namespace isc::data; - -namespace { -const Name authors_name("authors.bind"); -const Name version_name("version.bind"); -} - -void -DataSourceParkingLot::serve(std::string zone_name) { - zones.serve(zone_name); -} - -void -DataSourceParkingLot::addARecord(std::string data) { - a_records.push_back(RdataPtr(new in::A(data))); -} - -void -DataSourceParkingLot::addAAAARecord(std::string data) { - aaaa_records.push_back(RdataPtr(new in::AAAA(data))); -} - -void -DataSourceParkingLot::addNSRecord(std::string data) { - ns_records.push_back(RdataPtr(new generic::NS(data))); -} - -void -DataSourceParkingLot::setSOARecord(RdataPtr soa_record) { -} - -void -DataSourceParkingLot::setDefaultZoneData() { - clearARecords(); - clearAAAARecords(); - clearNSRecords(); - - addARecord("127.0.0.1"); - addAAAARecord("::1"); - addNSRecord("ns1.parking.example"); - addNSRecord("ns2.parking.example"); - addNSRecord("ns3.parking.example"); -} - -DataSourceParkingLot::DataSourceParkingLot() { - setDefaultZoneData(); - soa = RdataPtr(new generic::SOA(Name("parking.example"), - Name("noc.parking.example"), - 1, 1800, 900, 604800, 86400)); -} - -bool -DataSourceParkingLot::hasZoneFor(const Name& name, Name &zone_name) -{ - if (name == authors_name) { - zone_name = authors_name; - return (true); - } else if (name == version_name) { - zone_name = version_name; - return (true); - } - return zones.findClosest(name, zone_name); -} - -SearchResult -DataSourceParkingLot::findRRsets(const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const -{ - SearchResult result; - - if (clas == RRClass::CH()) { - if (type == RRType::TXT()) { - if (name == authors_name) { - RRsetPtr rrset = RRsetPtr(new RRset(authors_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - rrset->addRdata(generic::TXT("Han Feng")); - rrset->addRdata(generic::TXT("Kazunori Fujiwara")); - rrset->addRdata(generic::TXT("Michael Graff")); - rrset->addRdata(generic::TXT("Evan Hunt")); - rrset->addRdata(generic::TXT("Jelte Jansen")); - rrset->addRdata(generic::TXT("Jin Jian")); - rrset->addRdata(generic::TXT("JINMEI Tatuya")); - rrset->addRdata(generic::TXT("Naoki Kambe")); - rrset->addRdata(generic::TXT("Shane Kerr")); - rrset->addRdata(generic::TXT("Zhang Likun")); - rrset->addRdata(generic::TXT("Jeremy C. Reed")); - - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else if (name == version_name) { - RRsetPtr rrset = RRsetPtr(new RRset(version_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - rrset->addRdata(generic::TXT("BIND10 0.0.1")); - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else { - result.setStatus(SearchResult::name_not_found); - } - } else if (type == RRType::NS()) { - if (name == authors_name || name == version_name) { - RRsetPtr rrset = RRsetPtr(new RRset(name, RRClass::CH(), - RRType::NS(), - RRTTL(0))); - rrset->addRdata(generic::NS(name)); - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else { - result.setStatus(SearchResult::name_not_found); - } - } else { - result.setStatus(SearchResult::name_not_found); - } - } else if (clas == RRClass::IN()) { - if (zones.contains(name)) { - RRsetPtr rrset = RRsetPtr(new RRset(name, clas, type, RRTTL(3600))); - result.setStatus(SearchResult::success); - if (type == RRType::A()) { - BOOST_FOREACH(RdataPtr a, a_records) { - rrset->addRdata(a); - } - } else if (type == RRType::AAAA()) { - BOOST_FOREACH(RdataPtr aaaa, aaaa_records) { - rrset->addRdata(aaaa); - } - } else if (type == RRType::NS()) { - BOOST_FOREACH(RdataPtr ns, ns_records) { - rrset->addRdata(ns); - } - } else if (type == RRType::SOA()) { - rrset->addRdata(soa); - } - result.addRRset(rrset); - } else { - // we don't have the name itself. Do we have the zone? - if (zones.contains(zone_name)) { - result.setStatus(SearchResult::name_not_found); - } else { - result.setStatus(SearchResult::zone_not_found); - } - } - } else { - result.setStatus(SearchResult::zone_not_found); - } - return result; -} - -/// Do direct 'search' in database, no extra processing, -/// and add the resulting rrsets to the specified section -/// in the given message -/// returns the status code of the searchresult -/// Once the dns logic is moved from parkinglot to this class, -/// we should probably make this private -SearchResult::status_type -DataSourceParkingLot::addToMessage(Message& msg, - const Section& section, - const Name& zone_name, - const Name& name, - const RRClass& clas, - const RRType& type) const -{ - SearchResult result = findRRsets(zone_name, name, clas, type); - BOOST_FOREACH(RRsetPtr rrset, result) { - msg.addRRset(section, rrset); - } - return result.getStatus(); -} - - -} -} diff --git a/src/bin/parkinglot/data_source_plot.h b/src/bin/parkinglot/data_source_plot.h deleted file mode 100644 index 69b21256df..0000000000 --- a/src/bin/parkinglot/data_source_plot.h +++ /dev/null @@ -1,96 +0,0 @@ - -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __DATA_SOURCE_SIMPLE_H -#define __DATA_SOURCE_SIMPLE_H - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "common.h" - -#include "data_source.h" - -#include "zoneset.h" - -namespace isc { -namespace dns { - -class DataSourceParkingLot : public DataSource { -public: - DataSourceParkingLot(); - - void init() {}; - void close() {}; - bool hasZoneFor(const Name& name, Name &zone_name); - SearchResult findRRsets(const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const; - - /* move these to private (or to zoneset) and the calling functions - * from parkinglot to here? */ - void serve(std::string zone_name); - void clear_zones() { zones.clear_zones(); }; - - void clearARecords() { a_records.clear(); }; - void clearAAAARecords() { aaaa_records.clear(); }; - void clearNSRecords() { ns_records.clear(); }; - - void addARecord(std::string data); - void addAAAARecord(std::string data); - void addNSRecord(std::string data); - - void setSOARecord(isc::dns::rdata::RdataPtr soa_record); - - /// Do direct 'search' in database, no extra processing, - /// and add the resulting rrsets to the specified section - /// in the given message - /// Once the dns logic is moved from parkinglot to this class, - /// we should probably make this private - SearchResult::status_type addToMessage(Message& msg, - const isc::dns::Section& section, - const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const; - -private: - // - void setDefaultZoneData(); - - std::vector a_records, aaaa_records, ns_records; - isc::dns::rdata::RdataPtr soa; - ZoneSet zones; - -}; - -} -} - -#endif - -// Local Variables: -// mode: c++ -// End: diff --git a/src/bin/parkinglot/main.cc b/src/bin/parkinglot/main.cc deleted file mode 100644 index b547c795a1..0000000000 --- a/src/bin/parkinglot/main.cc +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include "zoneset.h" -#include "b10-parkinglot.h" - -#include "common.h" - -#include - -#include "config.h" - -using namespace std; - -//const string PROGRAM = "ParkingLot"; -const int DNSPORT = 5300; - -/* need global var for config/command handlers. - * todo: turn this around, and put handlers in the parkinglot - * class itself? */ -ParkingLot plot = ParkingLot(DNSPORT); - -static void -usage() { - cerr << "Usage: parkinglot [-p port]" << endl; - exit(1); -} - -isc::data::ElementPtr -my_config_handler(isc::data::ElementPtr config) -{ - return plot.updateConfig(config); -} - -isc::data::ElementPtr -my_command_handler(isc::data::ElementPtr command) -{ - isc::data::ElementPtr answer = isc::data::Element::createFromString("{ \"result\": [0] }"); - - if (command->get(0)->stringValue() == "print_message") - { - cout << command->get(1)->get("message") << endl; - /* let's add that message to our answer as well */ - answer->get("result")->add(command->get(1)); - } - - return answer; -} - -int -main(int argc, char* argv[]) { - int ch; - int port = DNSPORT; - - while ((ch = getopt(argc, argv, "p:")) != -1) { - switch (ch) { - case 'p': - port = atoi(optarg); - break; - case '?': - default: - usage(); - } - } - - if (argc - optind > 0) - usage(); - - // initialize parking lot - //plot = ParkingLot(port); - - // initialize command channel - try { - std::string specfile; - if (getenv("B10_FROM_SOURCE")) { - specfile = std::string(getenv("B10_FROM_SOURCE")) + "/src/bin/parkinglot/parkinglot.spec"; - } else { - specfile = std::string(PARKINGLOT_SPECFILE_LOCATION); - } - CommandSession cs = CommandSession(specfile, my_config_handler, my_command_handler); - - // main server loop - fd_set fds; - int ps = plot.getSocket(); - int ss = cs.getSocket(); - int nfds = max(ps, ss) + 1; - int counter = 0; - - cout << "[parkinglot] Server started." << endl; - while (true) { - FD_ZERO(&fds); - FD_SET(ps, &fds); - FD_SET(ss, &fds); - - int n = select(nfds, &fds, NULL, NULL, NULL); - if (n < 0) - throw FatalError("[parkinglot] select error"); - - if (FD_ISSET(ps, &fds)) { - ++counter; - plot.processMessage(); - } - - /* isset not really necessary, but keep it for now */ - if (FD_ISSET(ss, &fds)) { - cs.check_command(); - } - } - } catch (isc::cc::SessionError se) { - cout << se.what() << endl; - exit(1); - } - - return (0); -} diff --git a/src/bin/parkinglot/parkinglot.spec b/src/bin/parkinglot/parkinglot.spec deleted file mode 100644 index 9ac75792e2..0000000000 --- a/src/bin/parkinglot/parkinglot.spec +++ /dev/null @@ -1,74 +0,0 @@ -{ - "data_specification": { - "module_name": "ParkingLot", - "config_data": [ - { - "item_name": "port", - "item_type": "integer", - "item_optional": false, - "item_default": 5300 - }, - { - "item_name": "zones", - "item_type": "list", - "item_optional": false, - "item_default": [ ], - "list_item_spec": { - "item_name": "zone_name", - "item_type": "string", - "item_optional": false, - "item_default": "" - } - }, - { - "item_name": "a_records", - "item_type": "list", - "item_optional": false, - "item_default": [ "127.0.0.1" ], - "list_item_spec": { - "item_name": "address", - "item_type": "string", - "item_optional": false, - "item_default": "" - } - }, - { - "item_name": "aaaa_records", - "item_type": "list", - "item_optional": false, - "item_default": [ "::1" ], - "list_item_spec": { - "item_name": "address", - "item_type": "string", - "item_optional": false, - "item_default": "" - } - }, - { - "item_name": "ns_records", - "item_type": "list", - "item_optional": false, - "item_default": [ "ns1.parking.example", "ns2.parking.example" ], - "list_item_spec": { - "item_name": "address", - "item_type": "string", - "item_optional": false, - "item_default": "" - } - } - ], - "commands": [ - { - "command_name": "print_message", - "command_description": "Print the given message to stdout", - "command_args": [ { - "item_name": "message", - "item_type": "string", - "item_optional": False, - "item_default": "" - } ] - } - ] - } -} - diff --git a/src/bin/parkinglot/zoneset.cc b/src/bin/parkinglot/zoneset.cc deleted file mode 100644 index aa8fe2ffa7..0000000000 --- a/src/bin/parkinglot/zoneset.cc +++ /dev/null @@ -1,37 +0,0 @@ -#include "zoneset.h" - -// see findClosest() -static bool -stripLabel(isc::dns::Name& n) { - std::string str = n.toText(false); - size_t pos = str.find('.'); - if (pos + 1 < str.size()) { - n = isc::dns::Name(str.substr(pos+1)); - return true; - } else { - return false; - } -} - -bool -ZoneSet::findClosest(const isc::dns::Name& n, isc::dns::Name& closest) { - - // name compare doesnt work in this branch, so we convert and - // strip labels - isc::dns::Name cur = n; - if (contains(n)) { - closest = n; - return true; - } - - bool labels_left = stripLabel(cur); - while(labels_left) { - if (contains(cur)) { - closest = cur; - return true; - } else { - labels_left = stripLabel(cur); - } - } - return false; -} diff --git a/src/bin/parkinglot/zoneset.h b/src/bin/parkinglot/zoneset.h deleted file mode 100644 index 45a5a3ae4a..0000000000 --- a/src/bin/parkinglot/zoneset.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __ZONESET_H -#define __ZONESET_H 1 - -#include -#include -#include - -class ZoneSet { -public: - void serve(const std::string& s) { - serve(isc::dns::Name(s)); - } - - void serve(const isc::dns::Name& n) { - elements.insert(n); - } - - void forget(const std::string& s) { - forget(isc::dns::Name(s)); - } - - void forget(const isc::dns::Name& n) { - elements.erase(n); - } - - void clear_zones() { - elements.clear(); - } - - bool contains(const std::string& s) const { - return contains(isc::dns::Name(s)); - } - - bool contains(const isc::dns::Name& n) const { - return (elements.find(n) != elements.end()); - } - - bool findClosest(const isc::dns::Name& n, isc::dns::Name& closest); - -private: - std::set elements; -}; - -#endif // __ZONESET_H - -// Local Variables: -// mode: c++ -// End: diff --git a/src/lib/cc/cpp/parkinglot.data b/src/lib/cc/cpp/parkinglot.data deleted file mode 100644 index 495d0cfa6b..0000000000 --- a/src/lib/cc/cpp/parkinglot.data +++ /dev/null @@ -1,12 +0,0 @@ -{ - "port": 5300, - "zones": [ - { - "zone_name": "tjeb.nl" - }, - { - "zone_name": "jinmei.org" - } - ] -} - diff --git a/src/lib/cc/cpp/parkinglot.spec b/src/lib/cc/cpp/parkinglot.spec deleted file mode 100644 index 4b6e98abee..0000000000 --- a/src/lib/cc/cpp/parkinglot.spec +++ /dev/null @@ -1,46 +0,0 @@ -{ - "data_specification": { - "module_name": "ParkingLot", - "config_data": [ - { - "item_name": "port", - "item_type": "integer", - "item_optional": false, - "item_default": 5300 - }, - { - "item_name": "zones", - "item_type": "list", - "item_optional": false, - "item_default": [ ], - "list_item_spec": { - "item_name": "zone_name", - "item_type": "string", - "item_optional": false, - "item_default": "" - } - } - ], - "commands": [ - { - "command_name": "zone_add", - "command_args": [ { - "item_name": "zone_name", - "item_type": "string", - "item_optional": false, - "item_default": "" - } ] - }, - { - "command_name": "zone_delete", - "command_args": [ { - "item_name": "zone_name", - "item_type": "string", - "item_optional": false, - "item_default": "" - } ] - } - ] - } -} - From 2784c595fdf14331a159e27b360faa57454ababe Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 10 Feb 2010 19:11:57 +0000 Subject: [PATCH 08/81] Add AC_SEARCH_LIBS to add libraries to LIBS as noticed on Solaris10. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@785 e5f2f494-b856-4b98-b285-d166d9295462 --- configure.ac | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configure.ac b/configure.ac index 0d8e11ce3b..354d428cfa 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,9 @@ fi # Checks for libraries. +AC_SEARCH_LIBS(inet_pton, [nsl]) +AC_SEARCH_LIBS(recvfrom, [socket]) + # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. From eb27d3af6c688fed30b90532d3b0815290154613 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 10 Feb 2010 19:17:40 +0000 Subject: [PATCH 09/81] Add two missing boost headers as seen on Solaris10 build system (which doesn't have boost installed). git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@786 e5f2f494-b856-4b98-b285-d166d9295462 --- ext/boost/assign/list_inserter.hpp | 400 +++++++++++++++++++++++++++++ ext/boost/assign/std/vector.hpp | 37 +++ tools/import_boost.sh | 3 + 3 files changed, 440 insertions(+) create mode 100644 ext/boost/assign/list_inserter.hpp create mode 100644 ext/boost/assign/std/vector.hpp diff --git a/ext/boost/assign/list_inserter.hpp b/ext/boost/assign/list_inserter.hpp new file mode 100644 index 0000000000..eef071297e --- /dev/null +++ b/ext/boost/assign/list_inserter.hpp @@ -0,0 +1,400 @@ +// Boost.Assign library +// +// Copyright Thorsten Ottosen 2003-2004. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// For more information, see http://www.boost.org/libs/assign/ +// + +#ifndef BOOST_ASSIGN_LIST_INSERTER_HPP +#define BOOST_ASSIGN_LIST_INSERTER_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost +{ +namespace assign_detail +{ + template< class T > + struct repeater + { + std::size_t sz; + T val; + + repeater( std::size_t sz, T r ) : sz( sz ), val( r ) + { } + }; + + template< class Fun > + struct fun_repeater + { + std::size_t sz; + Fun val; + + fun_repeater( std::size_t sz, Fun r ) : sz( sz ), val( r ) + { } + }; + + template< class C > + class call_push_back + { + C& c_; + public: + call_push_back( C& c ) : c_( c ) + { } + + template< class T > + void operator()( T r ) + { + c_.push_back( r ); + } + }; + + template< class C > + class call_push_front + { + C& c_; + public: + call_push_front( C& c ) : c_( c ) + { } + + template< class T > + void operator()( T r ) + { + c_.push_front( r ); + } + }; + + template< class C > + class call_push + { + C& c_; + public: + call_push( C& c ) : c_( c ) + { } + + template< class T > + void operator()( T r ) + { + c_.push( r ); + } + }; + + template< class C > + class call_insert + { + C& c_; + public: + call_insert( C& c ) : c_( c ) + { } + + template< class T > + void operator()( T r ) + { + c_.insert( r ); + } + }; + + template< class C > + class call_add_edge + { + C& c_; + public: + call_add_edge( C& c ) : c_(c) + { } + + template< class T > + void operator()( T l, T r ) + { + add_edge( l, r, c_ ); + } + + template< class T, class EP > + void operator()( T l, T r, const EP& ep ) + { + add_edge( l, r, ep, c_ ); + } + + }; + + struct forward_n_arguments {}; + +} // namespace 'assign_detail' + +namespace assign +{ + + template< class T > + inline assign_detail::repeater + repeat( std::size_t sz, T r ) + { + return assign_detail::repeater( sz, r ); + } + + template< class Function > + inline assign_detail::fun_repeater + repeat_fun( std::size_t sz, Function r ) + { + return assign_detail::fun_repeater( sz, r ); + } + + + template< class Function, class Argument = assign_detail::forward_n_arguments > + class list_inserter + { + struct single_arg_type {}; + struct n_arg_type {}; + + typedef BOOST_DEDUCED_TYPENAME mpl::if_c< is_same::value, + n_arg_type, + single_arg_type >::type arg_type; + + public: + + list_inserter( Function fun ) : insert_( fun ) + {} + + template< class Function2, class Arg > + list_inserter( const list_inserter& r ) + : insert_( r.fun_private() ) + {} + + list_inserter( const list_inserter& r ) : insert_( r.insert_ ) + {} + + list_inserter& operator()() + { + insert_( Argument() ); + return *this; + } + + template< class T > + list_inserter& operator=( const T& r ) + { + insert_( r ); + return *this; + } + + template< class T > + list_inserter& operator=( assign_detail::repeater r ) + { + return operator,( r ); + } + + template< class Nullary_function > + list_inserter& operator=( const assign_detail::fun_repeater& r ) + { + return operator,( r ); + } + + template< class T > + list_inserter& operator,( const T& r ) + { + insert_( r ); + return *this; + } + +#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205)) + template< class T > + list_inserter& operator,( const assign_detail::repeater & r ) + { + return repeat( r.sz, r.val ); + } +#else + template< class T > + list_inserter& operator,( assign_detail::repeater r ) + { + return repeat( r.sz, r.val ); + } +#endif + + template< class Nullary_function > + list_inserter& operator,( const assign_detail::fun_repeater& r ) + { + return repeat_fun( r.sz, r.val ); + } + + template< class T > + list_inserter& repeat( std::size_t sz, T r ) + { + std::size_t i = 0; + while( i++ != sz ) + insert_( r ); + return *this; + } + + template< class Nullary_function > + list_inserter& repeat_fun( std::size_t sz, Nullary_function fun ) + { + std::size_t i = 0; + while( i++ != sz ) + insert_( fun() ); + return *this; + } + + template< class SinglePassIterator > + list_inserter& range( SinglePassIterator first, + SinglePassIterator last ) + { + for( ; first != last; ++first ) + insert_( *first ); + return *this; + } + + template< class SinglePassRange > + list_inserter& range( const SinglePassRange& r ) + { + return range( boost::begin(r), boost::end(r) ); + } + + template< class T > + list_inserter& operator()( const T& t ) + { + insert_( t ); + return *this; + } + +#ifndef BOOST_ASSIGN_MAX_PARAMS // use user's value +#define BOOST_ASSIGN_MAX_PARAMS 5 +#endif +#define BOOST_ASSIGN_MAX_PARAMETERS (BOOST_ASSIGN_MAX_PARAMS - 1) +#define BOOST_ASSIGN_PARAMS1(n) BOOST_PP_ENUM_PARAMS(n, class T) +#define BOOST_ASSIGN_PARAMS2(n) BOOST_PP_ENUM_BINARY_PARAMS(n, T, const& t) +#define BOOST_ASSIGN_PARAMS3(n) BOOST_PP_ENUM_PARAMS(n, t) + +#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS) +#define BOOST_PP_LOCAL_MACRO(n) \ + template< class T, BOOST_ASSIGN_PARAMS1(n) > \ + list_inserter& operator()(T t, BOOST_ASSIGN_PARAMS2(n) ) \ + { \ + BOOST_PP_CAT(insert, BOOST_PP_INC(n))(t, BOOST_ASSIGN_PARAMS3(n), arg_type()); \ + return *this; \ + } \ + /**/ + +#include BOOST_PP_LOCAL_ITERATE() + + +#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS) +#define BOOST_PP_LOCAL_MACRO(n) \ + template< class T, BOOST_ASSIGN_PARAMS1(n) > \ + void BOOST_PP_CAT(insert, BOOST_PP_INC(n))(T const& t, BOOST_ASSIGN_PARAMS2(n), single_arg_type) \ + { \ + insert_( Argument(t, BOOST_ASSIGN_PARAMS3(n) )); \ + } \ + /**/ + +#include BOOST_PP_LOCAL_ITERATE() + +#define BOOST_PP_LOCAL_LIMITS (1, BOOST_ASSIGN_MAX_PARAMETERS) +#define BOOST_PP_LOCAL_MACRO(n) \ + template< class T, BOOST_ASSIGN_PARAMS1(n) > \ + void BOOST_PP_CAT(insert, BOOST_PP_INC(n))(T const& t, BOOST_ASSIGN_PARAMS2(n), n_arg_type) \ + { \ + insert_(t, BOOST_ASSIGN_PARAMS3(n) ); \ + } \ + /**/ + +#include BOOST_PP_LOCAL_ITERATE() + + + Function fun_private() const + { + return insert_; + } + + private: + + list_inserter& operator=( const list_inserter& ); + Function insert_; + }; + + template< class Function > + inline list_inserter< Function > + make_list_inserter( Function fun ) + { + return list_inserter< Function >( fun ); + } + + template< class Function, class Argument > + inline list_inserter + make_list_inserter( Function fun, Argument* ) + { + return list_inserter( fun ); + } + + template< class C > + inline list_inserter< assign_detail::call_push_back, + BOOST_DEDUCED_TYPENAME C::value_type > + push_back( C& c ) + { + static BOOST_DEDUCED_TYPENAME C::value_type* p = 0; + return make_list_inserter( assign_detail::call_push_back( c ), + p ); + } + + template< class C > + inline list_inserter< assign_detail::call_push_front, + BOOST_DEDUCED_TYPENAME C::value_type > + push_front( C& c ) + { + static BOOST_DEDUCED_TYPENAME C::value_type* p = 0; + return make_list_inserter( assign_detail::call_push_front( c ), + p ); + } + + template< class C > + inline list_inserter< assign_detail::call_insert, + BOOST_DEDUCED_TYPENAME C::value_type > + insert( C& c ) + { + static BOOST_DEDUCED_TYPENAME C::value_type* p = 0; + return make_list_inserter( assign_detail::call_insert( c ), + p ); + } + + template< class C > + inline list_inserter< assign_detail::call_push, + BOOST_DEDUCED_TYPENAME C::value_type > + push( C& c ) + { + static BOOST_DEDUCED_TYPENAME C::value_type* p = 0; + return make_list_inserter( assign_detail::call_push( c ), + p ); + } + + template< class C > + inline list_inserter< assign_detail::call_add_edge > + add_edge( C& c ) + { + return make_list_inserter( assign_detail::call_add_edge( c ) ); + } + +} // namespace 'assign' +} // namespace 'boost' + +#undef BOOST_ASSIGN_PARAMS1 +#undef BOOST_ASSIGN_PARAMS2 +#undef BOOST_ASSIGN_PARAMS3 +#undef BOOST_ASSIGN_MAX_PARAMETERS + +#endif diff --git a/ext/boost/assign/std/vector.hpp b/ext/boost/assign/std/vector.hpp new file mode 100644 index 0000000000..e9f3b7981e --- /dev/null +++ b/ext/boost/assign/std/vector.hpp @@ -0,0 +1,37 @@ +// Boost.Assign library +// +// Copyright Thorsten Ottosen 2003-2004. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// For more information, see http://www.boost.org/libs/assign/ +// + +#ifndef BOOST_ASSIGN_STD_VECTOR_HPP +#define BOOST_ASSIGN_STD_VECTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +#include +#include +#include + +namespace boost +{ +namespace assign +{ + + template< class V, class A, class V2 > + inline list_inserter< assign_detail::call_push_back< std::vector >, V > + operator+=( std::vector& c, V2 v ) + { + return push_back( c )( v ); + } + +} +} + +#endif diff --git a/tools/import_boost.sh b/tools/import_boost.sh index b5ced8ac4d..e3d3f2359d 100755 --- a/tools/import_boost.sh +++ b/tools/import_boost.sh @@ -13,11 +13,14 @@ # svn commit # need new boost stuff? +# TODO: LICENSE_1_0.txt # add files to list 'ere FILES=" boost/*.hpp boost/algorithm boost/asio +boost/assign/list_inserter.hpp +boost/assign/std/vector.hpp boost/bind boost/config boost/concept From c2fb21d9b3acfb3c013f9e1c15e3249e161b0556 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 10 Feb 2010 21:07:37 +0000 Subject: [PATCH 10/81] added test/documentation for operator= and copy constructor (trac ticket #47, reviewed by Evan) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@788 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/name.h | 4 ++++ src/lib/dns/cpp/name_unittest.cc | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/lib/dns/cpp/name.h b/src/lib/dns/cpp/name.h index f5e8d66994..9f07affd6c 100644 --- a/src/lib/dns/cpp/name.h +++ b/src/lib/dns/cpp/name.h @@ -261,7 +261,11 @@ public: /// \param buffer A buffer storing the wire format data. /// \param downcase Whether to convert upper case alphabets to lower case. explicit Name(InputBuffer& buffer, bool downcase = false); + /// + /// We use the default copy constructor intentionally. //@} + /// We use the default copy assignment operator intentionally. + /// /// /// \name Getter Methods diff --git a/src/lib/dns/cpp/name_unittest.cc b/src/lib/dns/cpp/name_unittest.cc index 0fbf4666f1..ae8ebcf9c4 100644 --- a/src/lib/dns/cpp/name_unittest.cc +++ b/src/lib/dns/cpp/name_unittest.cc @@ -266,6 +266,36 @@ TEST_F(NameTest, fromWire) 25).getLabelCount()); } +TEST_F(NameTest, copyConstruct) +{ + Name copy(example_name); + EXPECT_EQ(copy, example_name); + + // Check the copied data is valid even after the original is deleted + Name* copy2 = new Name(example_name); + Name copy3(*copy2); + delete copy2; + EXPECT_EQ(copy3, example_name); +} + +TEST_F(NameTest, assignment) +{ + Name copy("."); + copy = example_name; + EXPECT_EQ(copy, example_name); + + // Check if the copied data is valid even after the original is deleted + Name* copy2 = new Name(example_name); + Name copy3("."); + copy3 = *copy2; + delete copy2; + EXPECT_EQ(copy3, example_name); + + // Self assignment + copy = copy; + EXPECT_EQ(copy, example_name); +} + TEST_F(NameTest, toText) { // tests derived from BIND9 From a568683bf2c2be2478e42f3201596ceb576bb55d Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 10 Feb 2010 23:00:58 +0000 Subject: [PATCH 11/81] added quick hack implementation of the SOA rdata class for datasource development. passed minimum level of unit test, will get it reviewed in the next review cycle. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@789 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rdata/generic/soa_6.cc | 31 +++++++++++++++++++++++--- src/lib/dns/cpp/rdata_unittest.cc | 6 ++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/lib/dns/cpp/rdata/generic/soa_6.cc b/src/lib/dns/cpp/rdata/generic/soa_6.cc index 9a076dd9db..2977c88091 100644 --- a/src/lib/dns/cpp/rdata/generic/soa_6.cc +++ b/src/lib/dns/cpp/rdata/generic/soa_6.cc @@ -39,10 +39,35 @@ SOA::SOA(InputBuffer& buffer, size_t rdata_len) : buffer.readData(numdata_, sizeof(numdata_)); } -SOA::SOA(const std::string& soastr) : - mname_("."), rname_(".") +SOA::SOA(const string& soastr) : + mname_("."), rname_(".") // quick hack workaround { - dns_throw(InvalidRdataText, "Not implemented yet"); + istringstream iss(soastr); + string token; + + iss >> token; + if (iss.bad() || iss.fail()) { + dns_throw(InvalidRdataText, "Invalid SOA MNAME"); + } + mname_ = Name(token); + iss >> token; + rname_ = Name(token); + if (iss.bad() || iss.fail()) { + dns_throw(InvalidRdataText, "Invalid SOA RNAME"); + } + + uint32_t serial, refresh, retry, expire, minimum; + iss >> serial >> refresh >> retry >> expire >> minimum; + if (iss.rdstate() != ios::eofbit) { + dns_throw(InvalidRdataText, "Invalid SOA format"); + } + OutputBuffer buffer(20); + buffer.writeUint32(serial); + buffer.writeUint32(refresh); + buffer.writeUint32(retry); + buffer.writeUint32(expire); + buffer.writeUint32(minimum); + memcpy(numdata_, buffer.getData(), buffer.getLength()); } SOA::SOA(const Name& mname, const Name& rname, uint32_t serial, diff --git a/src/lib/dns/cpp/rdata_unittest.cc b/src/lib/dns/cpp/rdata_unittest.cc index 09e37836fb..576e3c8ad7 100644 --- a/src/lib/dns/cpp/rdata_unittest.cc +++ b/src/lib/dns/cpp/rdata_unittest.cc @@ -330,7 +330,11 @@ TEST_F(RdataTest, getNSName) TEST_F(RdataTest, createFromText_SOA) { - //TBD + EXPECT_EQ(0, rdata_soa.compare(generic::SOA("ns.example.com. " + "root.example.com. " + "2010012601 3600 300 3600000 " + "1200"))); + // TBD: more tests } TEST_F(RdataTest, createFromWire_SOA) From a89bee850967ca74f7462dd92ce5d022a297d8f7 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 10 Feb 2010 23:01:16 +0000 Subject: [PATCH 12/81] propset git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@790 e5f2f494-b856-4b98-b285-d166d9295462 From 40cd668476c946dd7f8c03655798f5b9b2ef252f Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 10 Feb 2010 23:06:00 +0000 Subject: [PATCH 13/81] a minor bug fix: check the iss state before trying to use it git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@791 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rdata/generic/soa_6.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/dns/cpp/rdata/generic/soa_6.cc b/src/lib/dns/cpp/rdata/generic/soa_6.cc index 2977c88091..2c6db7f1c3 100644 --- a/src/lib/dns/cpp/rdata/generic/soa_6.cc +++ b/src/lib/dns/cpp/rdata/generic/soa_6.cc @@ -51,10 +51,10 @@ SOA::SOA(const string& soastr) : } mname_ = Name(token); iss >> token; - rname_ = Name(token); if (iss.bad() || iss.fail()) { dns_throw(InvalidRdataText, "Invalid SOA RNAME"); } + rname_ = Name(token); uint32_t serial, refresh, retry, expire, minimum; iss >> serial >> refresh >> retry >> expire >> minimum; From 8e06ee7688ba12e41f29d131dbffc8506aec55cc Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 12 Feb 2010 14:36:24 +0000 Subject: [PATCH 14/81] found bug when to/fromwiring empty lists added unittest git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@809 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/cc/cpp/data.cc | 20 +++++++++++++++----- src/lib/cc/cpp/data_unittests.cc | 8 ++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/lib/cc/cpp/data.cc b/src/lib/cc/cpp/data.cc index 855e292e8b..ee4827b799 100644 --- a/src/lib/cc/cpp/data.cc +++ b/src/lib/cc/cpp/data.cc @@ -829,13 +829,19 @@ ListElement::toWire(std::stringstream& ss, int omit_length) (*it)->toWire(ss2, 0); } + if (omit_length) { - ss << ss2.rdbuf(); + stringbuf *ss2_buf = ss2.rdbuf(); + if (ss2_buf->in_avail() > 0) { + ss << ss2_buf; + } } else { stringbuf *ss2_buf = ss2.rdbuf(); ss2_buf->pubseekpos(0); ss << encode_length(ss2_buf->in_avail(), ITEM_LIST); - ss << ss2_buf; + if (ss2_buf->in_avail() > 0) { + ss << ss2_buf; + } } } @@ -873,13 +879,17 @@ MapElement::toWire(std::stringstream& ss, int omit_length) // add length if needed // if (omit_length) { - ss << ss2.rdbuf(); + stringbuf *ss2_buf = ss2.rdbuf(); + if (ss2_buf->in_avail() > 0) { + ss << ss2_buf; + } } else { - stringbuf *ss2_buf = ss2.rdbuf(); ss2_buf->pubseekpos(0); ss << encode_length(ss2_buf->in_avail(), ITEM_HASH); - ss << ss2_buf; + if (ss2_buf->in_avail() > 0) { + ss << ss2_buf; + } } } diff --git a/src/lib/cc/cpp/data_unittests.cc b/src/lib/cc/cpp/data_unittests.cc index 875b7deac3..44a74a6f43 100644 --- a/src/lib/cc/cpp/data_unittests.cc +++ b/src/lib/cc/cpp/data_unittests.cc @@ -266,5 +266,13 @@ TEST(Element, to_and_from_wire) { //EXPECT_EQ("\047\0031.2", Element::create(1.2)->toWire(0)); EXPECT_EQ("\046\0011", Element::createFromString("[ 1 ]")->toWire(1)); + + std::string ddef = "{\"data_specification\": {\"config_data\": [ {\"item_default\": \"Hello, world!\", \"item_name\": \"default_name\", \"item_optional\": False, \"item_type\": \"string\"}, {\"item_default\": [ ], \"item_name\": \"zone_list\", \"item_optional\": False, \"item_type\": \"list\", \"list_item_spec\": {\"item_name\": \"zone_name\", \"item_optional\": True, \"item_type\": \"string\"}} ], \"module_name\": \"Auth\"}}"; + //std::string ddef = "{\"aaa\": 123, \"test\": [ ], \"zzz\": 123}"; + ElementPtr ddef_el = Element::createFromString(ddef); + std::string ddef_wire = ddef_el->toWire(); + ElementPtr ddef_el2 = Element::fromWire(ddef_wire); + std::string ddef2 = ddef_el2->str(); + EXPECT_EQ(ddef, ddef2); } From b302cfa381dc321986d195203c9f3176c126457c Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Sat, 13 Feb 2010 22:07:58 +0000 Subject: [PATCH 15/81] refactoring of cfgmgr and config in general; they now use the datadefinition class so they could later validate data that is passed around (refactoring not done yet, though it is now in a working state again, which seemed like a good time to commit) added a config_data.py with classes for storing definitions and data (for both modules and UIs) fixed a missed refactoring bug in bob changed DataDefinition initializer; a string is now parsed instead of seen as a file name; there is a helper function in the module to read a datadef directly from file now added a temporary example config data specification for auth module added a temporary second config data element to bob.spec git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@814 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth.spec | 20 +- src/bin/bind10/bind10.py.in | 2 +- src/bin/bind10/bob.spec | 6 + src/bin/bindctl/bindcmd.py | 15 +- src/bin/bindctl/bindctl.in | 2 +- src/bin/bindctl/bindctl.py | 19 +- src/lib/cc/python/isc/cc/data.py | 262 +------- src/lib/config/python/isc/config/__init__.py | 1 + src/lib/config/python/isc/config/ccsession.py | 4 +- src/lib/config/python/isc/config/cfgmgr.py | 91 ++- .../config/python/isc/config/cfgmgr_test.py | 58 +- .../config/python/isc/config/config_data.py | 576 ++++++++++++++++++ .../python/isc/config/config_data_test.py | 41 ++ .../python/isc/config/datadefinition.py | 79 +-- .../python/isc/config/datadefinition_test.py | 21 +- src/lib/config/testdata/spec2.spec | 17 + src/lib/config/testdata/spec3.spec | 2 +- 17 files changed, 836 insertions(+), 380 deletions(-) create mode 100644 src/lib/config/python/isc/config/config_data.py create mode 100644 src/lib/config/python/isc/config/config_data_test.py diff --git a/src/bin/auth/auth.spec b/src/bin/auth/auth.spec index 6c37bf375a..f3b4b7f300 100644 --- a/src/bin/auth/auth.spec +++ b/src/bin/auth/auth.spec @@ -1,6 +1,24 @@ { "data_specification": { - "module_name": "ParkingLot" + "module_name": "Auth", + "config_data": [ + { "item_name": "default_name", + "item_type": "string", + "item_optional": False, + "item_default": "Hello, world!" + }, + { "item_name": "zone_list", + "item_type": "list", + "item_optional": False, + "item_default": [], + "list_item_spec": + { "item_name": "zone_name", + "item_type": "string", + "item_optional": True, + "item_default": "" + } + } + ] } } diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index aecc95fc45..adf265b61a 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -478,7 +478,7 @@ def main(): for fd in rlist + xlist: if fd == ccs_fd: - boss_of_bind.ccs.checkCommand() + boss_of_bind.ccs.check_command() elif fd == wakeup_fd: os.read(wakeup_fd, 32) diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec index f6df7adb0e..230c35e0ba 100644 --- a/src/bin/bind10/bob.spec +++ b/src/bin/bind10/bob.spec @@ -7,6 +7,12 @@ "item_type": "string", "item_optional": False, "item_default": "Hi, shane!" + }, + { + "item_name": "some_other_string", + "item_type": "string", + "item_optional": False, + "item_default": "Hi, shane!" } ], "commands": [ diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index 063399f8b6..908d081bad 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -85,7 +85,7 @@ class BindCmdInterpreter(Cmd): return False # Get all module information from cmd-ctrld - self.config_data = isc.cc.data.UIConfigData(self) + self.config_data = isc.config.UIConfigData(self) self.update_commands() self.cmdloop() except KeyboardInterrupt: @@ -150,7 +150,8 @@ class BindCmdInterpreter(Cmd): if (len(cmd_spec) == 0): print('can\'t get any command specification') for module_name in cmd_spec.keys(): - self.prepare_module_commands(module_name, cmd_spec[module_name]) + if cmd_spec[module_name]: + self.prepare_module_commands(module_name, cmd_spec[module_name]) def send_GET(self, url, body = None): headers = {"cookie" : self.session_id} @@ -315,7 +316,7 @@ class BindCmdInterpreter(Cmd): if cmd.module == "config": # grm text has been stripped of slashes... my_text = self.location + "/" + cur_line.rpartition(" ")[2] - list = self.config_data.config.get_item_list(my_text.rpartition("/")[0]) + list = self.config_data.get_config_item_list(my_text.rpartition("/")[0]) hints.extend([val for val in list if val.startswith(text)]) except CmdModuleNameFormatError: if not text: @@ -440,17 +441,17 @@ class BindCmdInterpreter(Cmd): line += "(modified)" print(line) elif cmd.command == "add": - self.config_data.add(identifier, cmd.params['value']) + self.config_data.add_value(identifier, cmd.params['value']) elif cmd.command == "remove": - self.config_data.remove(identifier, cmd.params['value']) + self.config_data.remove_value(identifier, cmd.params['value']) elif cmd.command == "set": - self.config_data.set(identifier, cmd.params['value']) + self.config_data.set_value(identifier, cmd.params['value']) elif cmd.command == "unset": self.config_data.unset(identifier) elif cmd.command == "revert": self.config_data.revert() elif cmd.command == "commit": - self.config_data.commit(self) + self.config_data.commit() elif cmd.command == "go": self.go(identifier) except isc.cc.data.DataTypeError as dte: diff --git a/src/bin/bindctl/bindctl.in b/src/bin/bindctl/bindctl.in index 6526f69d85..3febaaa251 100644 --- a/src/bin/bindctl/bindctl.in +++ b/src/bin/bindctl/bindctl.in @@ -5,7 +5,7 @@ export PYTHON_EXEC BINDCTL_PATH=@abs_top_srcdir@/src/bin/bindctl -PYTHONPATH=@abs_top_srcdir@/src/lib/cc/python +PYTHONPATH=@abs_top_builddir@/pyshared export PYTHONPATH cd ${BINDCTL_PATH} diff --git a/src/bin/bindctl/bindctl.py b/src/bin/bindctl/bindctl.py index b1430df9c3..162177cb64 100644 --- a/src/bin/bindctl/bindctl.py +++ b/src/bin/bindctl/bindctl.py @@ -67,12 +67,17 @@ def prepare_config_commands(tool): tool.add_module_info(module) if __name__ == '__main__': - try: - tool = BindCmdInterpreter("localhost:8080") - prepare_config_commands(tool) - tool.run() - except Exception as e: - print(e) - print("Failed to connect with b10-cmdctl module, is it running?") + tool = BindCmdInterpreter("localhost:8080") + prepare_config_commands(tool) + tool.run() +# TODO: put below back, was removed to see errors +#if __name__ == '__main__': + #try: + #tool = BindCmdInterpreter("localhost:8080") + #prepare_config_commands(tool) + #tool.run() + #except Exception as e: + #print(e) + #print("Failed to connect with b10-cmdctl module, is it running?") diff --git a/src/lib/cc/python/isc/cc/data.py b/src/lib/cc/python/isc/cc/data.py index d5c6f79bd9..dc4772e052 100644 --- a/src/lib/cc/python/isc/cc/data.py +++ b/src/lib/cc/python/isc/cc/data.py @@ -84,9 +84,10 @@ def set(element, identifier, value): else: # set to none, and parent el not found, return return element - if value: + # value can be an empty list or dict, so check for None eplicitely + if value != None: cur_el[id_parts[-1]] = value - else: + elif id_parts[-1] in cur_el: del cur_el[id_parts[-1]] return element @@ -114,85 +115,6 @@ def find_no_exc(element, identifier): return None return cur_el -# -# hmm, these are more relevant for datadefition -# should we (re)move them? -# -def find_spec(element, identifier): - """find the data definition for the given identifier - returns either a map with 'item_name' etc, or a list of those""" - id_parts = identifier.split("/") - id_parts[:] = (value for value in id_parts if value != "") - cur_el = element - for id in id_parts: - if type(cur_el) == dict and id in cur_el.keys(): - cur_el = cur_el[id] - elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id: - pass - elif type(cur_el) == list: - found = False - for cur_el_item in cur_el: - if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys(): - cur_el = cur_el_item - found = True - if not found: - raise DataNotFoundError(id + " in " + str(cur_el)) - else: - raise DataNotFoundError(id + " in " + str(cur_el)) - return cur_el - -def check_type(specification, value): - """Returns true if the value is of the correct type given the - specification""" - if type(specification) == list: - data_type = "list" - else: - data_type = specification['item_type'] - - if data_type == "integer" and type(value) != int: - raise DataTypeError(str(value) + " should be an integer") - elif data_type == "real" and type(value) != double: - raise DataTypeError(str(value) + " should be a real") - elif data_type == "boolean" and type(value) != boolean: - raise DataTypeError(str(value) + " should be a boolean") - elif data_type == "string" and type(value) != str: - raise DataTypeError(str(value) + " should be a string") - elif data_type == "list": - if type(value) != list: - raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__)) - else: - # todo: check subtypes etc - for element in value: - check_type(specification['list_item_spec'], element) - elif data_type == "map" and type(value) != dict: - # todo: check subtypes etc - raise DataTypeError(str(value) + " should be a map") - -def spec_name_list(spec, prefix="", recurse=False): - """Returns a full list of all possible item identifiers in the - specification (part)""" - result = [] - if prefix != "" and not prefix.endswith("/"): - prefix += "/" - if type(spec) == dict: - for name in spec: - result.append(prefix + name + "/") - if recurse: - result.extend(spec_name_list(spec[name],name, recurse)) - elif type(spec) == list: - for list_el in spec: - if 'item_name' in list_el: - if list_el['item_type'] == dict: - if recurse: - result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse)) - else: - name = list_el['item_name'] - if list_el['item_type'] in ["list", "map"]: - name += "/" - result.append(name) - - return result - def parse_value_str(value_str): """Parses the given string to a native python object. If the string cannot be parsed, it is returned. If it is not a string, @@ -208,181 +130,3 @@ def parse_value_str(value_str): # simply return the string itself return value_str -class ConfigData: - def __init__(self, specification): - self.specification = specification - self.data = {} - - def get_item_list(self, identifier = None): - if identifier: - spec = find_spec(self.specification, identifier) - return spec_name_list(spec, identifier + "/") - return spec_name_list(self.specification) - - def get_value(self, identifier): - """Returns a tuple where the first item is the value at the - given identifier, and the second item is a bool which is - true if the value is an unset default""" - value = find_no_exc(self.data, identifier) - if value: - return value, False - spec = find_spec(self.specification, identifier) - if spec and 'item_default' in spec: - return spec['item_default'], True - return None, False - -class UIConfigData(): - def __init__(self, conn, name = ''): - self.module_name = name - data_spec = self.get_data_specification(conn) - self.config = ConfigData(data_spec) - self.get_config_data(conn) - self.config_changes = {} - - def get_config_data(self, conn): - self.config.data = conn.send_GET('/config_data') - - def send_changes(self, conn): - conn.send_POST('/ConfigManager/set_config', self.config_changes) - # Get latest config data - self.get_config_data(conn) - self.config_changes = {} - - def get_data_specification(self, conn): - return conn.send_GET('/config_spec') - - def set(self, identifier, value): - # check against definition - spec = find_spec(identifier) - check_type(spec, value) - set(self.config_changes, identifier, value) - - def get_value(self, identifier): - """Returns a three-tuple, where the first item is the value - (or None), the second is a boolean specifying whether - the value is the default value, and the third is a boolean - specifying whether the value is an uncommitted change""" - value = find_no_exc(self.config_changes, identifier) - if value: - return value, False, True - value, default = self.config.get_value(identifier) - if value: - return value, default, False - return None, False, False - - def get_value_map_single(self, identifier, entry): - """Returns a single entry for a value_map, where the value is - not a part of a bigger map""" - result_part = {} - result_part['name'] = entry['item_name'] - result_part['type'] = entry['item_type'] - value, default, modified = self.get_value(identifier) - # should we check type and only set int, double, bool and string here? - result_part['value'] = value - result_part['default'] = default - result_part['modified'] = modified - return result_part - - def get_value_map(self, identifier, entry): - """Returns a single entry for a value_map, where the value is - a part of a bigger map""" - result_part = {} - result_part['name'] = entry['item_name'] - result_part['type'] = entry['item_type'] - value, default, modified = self.get_value(identifier + "/" + entry['item_name']) - # should we check type and only set int, double, bool and string here? - result_part['value'] = value - result_part['default'] = default - result_part['modified'] = modified - return result_part - - def get_value_maps(self, identifier = None): - """Returns a list of maps, containing the following values: - name: name of the entry (string) - type: string containing the type of the value (or 'module') - value: value of the entry if it is a string, int, double or bool - modified: true if the value is a local change - default: true if the value has been changed - Throws DataNotFoundError if the identifier is bad - """ - spec = find_spec(self.config.specification, identifier) - result = [] - if type(spec) == dict: - # either the top-level list of modules or a spec map - if 'item_name' in spec: - result_part = self.get_value_map_single(identifier, spec) - if result_part['type'] == "list": - values = self.get_value(identifier)[0] - if values: - for value in values: - result_part2 = {} - li_spec = spec['list_item_spec'] - result_part2['name'] = li_spec['item_name'] - result_part2['value'] = value - result_part2['type'] = li_spec['item_type'] - result_part2['default'] = False - result_part2['modified'] = False - result.append(result_part2) - else: - result.append(result_part) - - else: - for name in spec: - result_part = {} - result_part['name'] = name - result_part['type'] = "module" - result_part['value'] = None - result_part['default'] = False - result_part['modified'] = False - result.append(result_part) - elif type(spec) == list: - for entry in spec: - if type(entry) == dict and 'item_name' in entry: - result.append(self.get_value_map(identifier, entry)) - return result - - def add(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): - raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(data_spec, [value]) - cur_list = find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = find_no_exc(self.config.data, identifier) - if not cur_list: - cur_list = [] - if value not in cur_list: - cur_list.append(value) - set(self.config_changes, identifier, cur_list) - - def remove(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): - raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(data_spec, [value]) - cur_list = find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = find_no_exc(self.config.data, identifier) - if not cur_list: - cur_list = [] - if value in cur_list: - cur_list.remove(value) - set(self.config_changes, identifier, cur_list) - - def set(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - value = parse_value_str(value_str) - check_type(data_spec, value) - set(self.config_changes, identifier, value) - - def unset(self, identifier): - # todo: check whether the value is optional? - unset(self.config_changes, identifier) - - def revert(self): - self.config_changes = {} - - def commit(self, conn): - self.send_changes(conn) diff --git a/src/lib/config/python/isc/config/__init__.py b/src/lib/config/python/isc/config/__init__.py index 37793b5fd0..920f95bfa5 100644 --- a/src/lib/config/python/isc/config/__init__.py +++ b/src/lib/config/python/isc/config/__init__.py @@ -1,2 +1,3 @@ from isc.config.ccsession import * +from isc.config.config_data import * from isc.config.datadefinition import * diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 8d48eb45db..358dfc0686 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -27,7 +27,7 @@ import isc class CCSession: def __init__(self, spec_file_name, config_handler, command_handler): - self._data_definition = isc.config.DataDefinition(spec_file_name) + self._data_definition = isc.config.data_spec_from_file(spec_file_name) self._module_name = self._data_definition.get_module_name() self.set_config_handler(config_handler) @@ -83,7 +83,7 @@ class CCSession: def __send_spec(self): """Sends the data specification to the configuration manager""" - self._session.group_sendmsg(self._data_definition.get_definition(), "ConfigManager") + self._session.group_sendmsg({ "data_specification": self._data_definition.get_definition() }, "ConfigManager") answer, env = self._session.group_recvmsg(False) def __get_full_config(self): diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 0fa1af9cd8..6b696008f5 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -106,9 +106,12 @@ 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): - self.commands = {} + # remove these and use self.data_specs + #self.commands = {} self.data_definitions = {} + self.data_path = data_path + self.data_specs = {} self.config = ConfigManagerData(data_path) if session: self.cc = session @@ -122,21 +125,40 @@ class ConfigManager: """Notifies the Boss module that the Config Manager is running""" self.cc.group_sendmsg({"running": "configmanager"}, "Boss") - def set_config(self, module_name, data_specification): - """Set the data specification for the given module""" - self.data_definitions[module_name] = data_specification - - def remove_config(self, module_name): - """Remove the data specification for the given module""" - self.data_definitions[module_name] + def set_data_spec(self, spec): + #data_def = isc.config.DataDefinition(spec) + self.data_specs[spec.get_module_name()] = spec - def set_commands(self, module_name, commands): - """Set the command list for the given module""" - self.commands[module_name] = commands + def get_data_spec(self, module_name): + if module_name in self.data_specs: + return self.data_specs[module_name] - def remove_commands(self, module_name): - """Remove the command list for the given module""" - del self.commands[module_name] + def get_config_data(self, name = None): + """Returns a dict containing 'module_name': config_data for + all modules. If name is specified, only that module will + be included""" + config_data = {} + if name: + if name in self.data_specs: + config_data[name] = self.data_specs[name].get_data + else: + for module_name in self.data_specs.keys(): + config_data[module_name] = self.data_specs[module_name].get_config_data() + return config_data + + def get_commands(self, name = None): + """Returns a dict containing 'module_name': commands_dict for + all modules. If name is specified, only that module will + be included""" + commands = {} + if name: + if name in self.data_specs: + commands[name] = self.data_specs[name].get_commands + else: + for module_name in self.data_specs.keys(): + print("[XX] add commands for " + module_name) + commands[module_name] = self.data_specs[module_name].get_commands() + return commands def read_config(self): """Read the current configuration from the b10-config.db file @@ -158,16 +180,13 @@ class ConfigManager: if type(cmd[1]) == dict: if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': module_name = cmd[1]['module_name'] - try: - answer["result"] = [0, self.data_definitions[module_name]] - except KeyError as ke: - answer["result"] = [1, "No specification for module " + module_name] + answer["result"] = [0, self.get_config_data(module_name)] else: answer["result"] = [1, "Bad module_name in get_data_spec command"] else: answer["result"] = [1, "Bad get_data_spec command, argument not a dict"] else: - answer["result"] = [0, self.data_definitions] + answer["result"] = [0, self.get_config_data()] return answer def _handle_get_config(self, cmd): @@ -201,6 +220,8 @@ class ConfigManager: self.cc.group_sendmsg({ "config_update": conf_part }, module_name) else: conf_part = data.set(self.config.data, module_name, {}) + print("[XX] SET CONF PART:") + print(conf_part) data.merge(conf_part[module_name], cmd[2]) # send out changed info self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) @@ -224,28 +245,37 @@ class ConfigManager: # todo: use DataDefinition class # todo: error checking (like keyerrors) answer = {} - if "config_data" in spec: - self.set_config(spec["module_name"], spec["config_data"]) - self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "Cmd-Ctrld") - if "commands" in spec: - self.set_commands(spec["module_name"], spec["commands"]) - self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "Cmd-Ctrld") + print("[XX] CFGMGR got spec:") + print(spec) + self.set_data_spec(spec) + + # We should make one general 'spec update for module' that + # passes both specification and commands at once + self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_data() ] }, "Cmd-Ctrld") + self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands() ] }, "Cmd-Ctrld") answer["result"] = [ 0 ] return answer def handle_msg(self, msg): """Handle a direct command""" answer = {} + print("[XX] cfgmgr got msg:") + print(msg) if "command" in msg: cmd = msg["command"] try: if cmd[0] == "get_commands": - answer["result"] = [ 0, self.commands ] - + answer["result"] = [ 0, self.get_commands() ] + print("[XX] get_commands answer:") + print(answer) elif cmd[0] == "get_data_spec": answer = self._handle_get_data_spec(cmd) + print("[XX] get_data_spec answer:") + print(answer) elif cmd[0] == "get_config": answer = self._handle_get_config(cmd) + print("[XX] get_config answer:") + print(answer) elif cmd[0] == "set_config": answer = self._handle_set_config(cmd) elif cmd[0] == "shutdown": @@ -258,12 +288,17 @@ class ConfigManager: answer["result"] = [ 1, "Missing argument in command: " + str(ie) ] raise ie elif "data_specification" in msg: - answer = self._handle_data_specification(msg["data_specification"]) + try: + answer = self._handle_data_specification(isc.config.DataDefinition(msg["data_specification"])) + except isc.config.DataDefinitionError as dde: + answer['result'] = [ 1, "Error in data definition: " + str(dde) ] elif 'result' in msg: # this seems wrong, might start pingpong answer['result'] = [0] else: answer["result"] = [ 1, "Unknown message format: " + str(msg) ] + print("[XX] cfgmgr sending answer:") + print(answer) return answer def run(self): diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index f566a566ad..8ab3443dcc 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -102,12 +102,10 @@ class TestConfigManager(unittest.TestCase): self.fake_session = FakeCCSession() self.cm = ConfigManager(self.data_path, self.fake_session) self.name = "TestModule" - self.spec = { "asdf" } - self.commands = { "bbbb" } + self.spec = isc.config.data_spec_from_file(self.data_path + os.sep + "/spec2.spec") def test_init(self): - self.assert_(self.cm.commands == {}) - self.assert_(self.cm.data_definitions == {}) + self.assert_(self.cm.data_specs == {}) self.assert_(self.cm.data_path == self.data_path) self.assert_(self.cm.config != None) self.assert_(self.fake_session.has_subscription("ConfigManager")) @@ -121,27 +119,26 @@ class TestConfigManager(unittest.TestCase): # this one is actually wrong, but 'current status quo' self.assertEqual(msg, {"running": "configmanager"}) - def test_set_config(self): - self.cm.set_config(self.name, self.spec) - self.assertEqual(self.cm.data_definitions[self.name], self.spec) + #def test_set_config(self): + #self.cm.set_config(self.name, self.spec) + #self.assertEqual(self.cm.data_definitions[self.name], self.spec) - def test_remove_config(self): - self.assertRaises(KeyError, self.cm.remove_config, self.name) - self.cm.set_config(self.name, self.spec) - self.cm.remove_config(self.name) + #def test_remove_config(self): + #self.assertRaises(KeyError, self.cm.remove_config, self.name) + #self.cm.set_config(self.name, self.spec) + #self.cm.remove_config(self.name) - def test_set_commands(self): - self.cm.set_commands(self.name, self.commands) - self.assertEqual(self.cm.commands[self.name], self.commands) + #def test_set_commands(self): + # self.cm.set_commands(self.name, self.commands) + # self.assertEqual(self.cm.commands[self.name], self.commands) - def test_write_config(self): - self.assertRaises(KeyError, self.cm.remove_commands, self.name) - self.cm.set_commands(self.name, self.commands) - self.cm.remove_commands(self.name) + #def test_write_config(self): + # self.assertRaises(KeyError, self.cm.remove_commands, self.name) + # self.cm.set_commands(self.name, self.commands) + # self.cm.remove_commands(self.name) def _handle_msg_helper(self, msg, expected_answer): answer = self.cm.handle_msg(msg) - #self.assertEquals(answer, expected_answer) self.assertEqual(expected_answer, answer) def test_handle_msg(self): @@ -150,8 +147,8 @@ class TestConfigManager(unittest.TestCase): self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: ['badcommand']"]}) self._handle_msg_helper({ "command": [ "get_commands" ] }, { 'result': [ 0, {} ]}) self._handle_msg_helper({ "command": [ "get_data_spec" ] }, { 'result': [ 0, {} ]}) - self._handle_msg_helper({ "command": [ "get_data_spec", { "module_name": "nosuchmodule" } ] }, - {'result': [1, 'No specification for module nosuchmodule']}) + #self._handle_msg_helper({ "command": [ "get_data_spec", { "module_name": "nosuchmodule" } ] }, + # {'result': [1, 'No specification for module nosuchmodule']}) self._handle_msg_helper({ "command": [ "get_data_spec", 1 ] }, {'result': [1, 'Bad get_data_spec command, argument not a dict']}) self._handle_msg_helper({ "command": [ "get_data_spec", { } ] }, @@ -181,16 +178,23 @@ class TestConfigManager(unittest.TestCase): self.fake_session.get_message(self.name, None)) self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) self._handle_msg_helper({ "data_specification": - { "module_name": self.name, "config_data": self.spec, "commands": self.commands } + self.spec.get_definition() }, {'result': [0]}) - self.assertEqual(len(self.fake_session.message_queue), 2) + self._handle_msg_helper({ "data_specification": + { 'foo': 1 } + }, + {'result': [1, 'Error in data definition: no module_name in data_specification']}) + self._handle_msg_helper({ "command": [ "get_data_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_config_spec() } ]}) + self._handle_msg_helper({ "command": [ "get_commands" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands() } ]}) + # re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages) + #self.assertEqual(len(self.fake_session.message_queue), 2) # the name here is actually wrong (and hardcoded), but needed in the current version # TODO: fix that - self.assertEqual({'specification_update': [ self.name, self.spec ] }, - self.fake_session.get_message("Cmd-Ctrld", None)) - self.assertEqual({'commands_update': [ self.name, self.commands ] }, - self.fake_session.get_message("Cmd-Ctrld", None)) + #self.assertEqual({'specification_update': [ self.name, self.spec ] }, + # self.fake_session.get_message("Cmd-Ctrld", None)) + #self.assertEqual({'commands_update': [ self.name, self.commands ] }, + # self.fake_session.get_message("Cmd-Ctrld", None)) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py new file mode 100644 index 0000000000..be438a3375 --- /dev/null +++ b/src/lib/config/python/isc/config/config_data.py @@ -0,0 +1,576 @@ +# Copyright (C) 2010 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. + +# +# Class to store configuration data and data definition +# Used by the config manager and python modules that communicate +# with the configuration manager +# + + +import isc.cc.data +import isc.config.datadefinition + +class ConfigDataError(Exception): pass + +# +# hmm, these are more relevant for datadefition +# should we (re)move them? +# +def check_type(specification, value): + """Returns true if the value is of the correct type given the + specification""" + if type(specification) == list: + data_type = "list" + else: + data_type = specification['item_type'] + + if data_type == "integer" and type(value) != int: + raise DataTypeError(str(value) + " should be an integer") + elif data_type == "real" and type(value) != double: + raise DataTypeError(str(value) + " should be a real") + elif data_type == "boolean" and type(value) != boolean: + raise DataTypeError(str(value) + " should be a boolean") + elif data_type == "string" and type(value) != str: + raise DataTypeError(str(value) + " should be a string") + elif data_type == "list": + if type(value) != list: + raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__)) + else: + # todo: check subtypes etc + for element in value: + check_type(specification['list_item_spec'], element) + elif data_type == "map" and type(value) != dict: + # todo: check subtypes etc + raise DataTypeError(str(value) + " should be a map") + +def find_spec(element, identifier): + """find the data definition for the given identifier + returns either a map with 'item_name' etc, or a list of those""" + if identifier == "": + return element + id_parts = identifier.split("/") + id_parts[:] = (value for value in id_parts if value != "") + cur_el = element + for id in id_parts: + if type(cur_el) == dict and id in cur_el.keys(): + cur_el = cur_el[id] + elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id: + pass + elif type(cur_el) == list: + found = False + for cur_el_item in cur_el: + if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys(): + cur_el = cur_el_item + found = True + if not found: + raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el)) + else: + raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el)) + return cur_el + +def spec_name_list(spec, prefix="", recurse=False): + """Returns a full list of all possible item identifiers in the + specification (part)""" + result = [] + if prefix != "" and not prefix.endswith("/"): + prefix += "/" + if type(spec) == dict: + for name in spec: + result.append(prefix + name + "/") + if recurse: + result.extend(spec_name_list(spec[name],name, recurse)) + elif type(spec) == list: + for list_el in spec: + if 'item_name' in list_el: + if list_el['item_type'] == dict: + if recurse: + result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse)) + else: + name = list_el['item_name'] + if list_el['item_type'] in ["list", "map"]: + name += "/" + result.append(name) + return result + + +class ConfigData: + """This class stores the datadefinition and the current non-default + config values. It provides functions to get the actual value or + the default value if no non-default value has been set""" + + def __init__(self, specification): + """Initialize a ConfigData instance. If specification is not + of type DataDefinition, a ConfigDataError is raised.""" + if type(specification) != isc.config.DataDefinition: + raise ConfigDataError("specification is of type " + str(type(specification)) + ", not DataDefinition") + self.specification = specification + self.data = {} + + def get_item_list(self, identifier = None): + if identifier: + spec = find_spec(self.specification, identifier) + return spec_name_list(spec, identifier + "/") + return spec_name_list(self.specification) + + def get_value(self, identifier): + """Returns a tuple where the first item is the value at the + given identifier, and the second item is a bool which is + true if the value is an unset default""" + value = find_no_exc(self.data, identifier) + if value: + return value, False + spec = find_spec(self.specification, identifier) + if spec and 'item_default' in spec: + return spec['item_default'], True + return None, False + +class MultiConfigData: + """This class stores the datadefinitions, current non-default + configuration values and 'local' (uncommitted) changes.""" + LOCAL = 1 + CURRENT = 2 + DEFAULT = 3 + NONE = 4 + + def __init__(self): + self._specifications = {} + self._current_config = {} + self._local_changes = {} + + def set_specification(self, spec): + if type(spec) != isc.config.DataDefinition: + raise Exception("not a datadef") + self._specifications[spec.get_module_name()] = spec + + def get_specification(self, module): + if module in self._specifications: + return self._specifications[module] + else: + return None + + def find_spec_part(self, identifier): + """returns the default value, or None if there is no default""" + if identifier[0] == '/': + identifier = identifier[1:] + module, sep, id = identifier.partition("/") + try: + return find_spec(self._specifications[module].get_config_data(), id) + except isc.cc.data.DataNotFoundError as dnfe: + return None + + def set_current_config(self, config): + self._current_config = config + + def get_current_config(self): + """The current config is a dict where the first level is + the module name, and the value is the config values for + that module""" + return self._current_config + + def get_local_changes(self): + return self._local_changes + + def clear_local_changes(self): + self._local_changes = {} + + def get_local_value(self, identifier): + return isc.cc.data.find_no_exc(self._local_changes, identifier) + + def get_current_value(self, identifier): + """Returns the current non-default value, or None if not set""" + return isc.cc.data.find_no_exc(self._current_config, identifier) + + def get_default_value(self, identifier): + """returns the default value, or None if there is no default""" + if identifier[0] == '/': + identifier = identifier[1:] + module, sep, id = identifier.partition("/") + try: + spec = find_spec(self._specifications[module].get_config_data(), id) + if 'item_default' in spec: + return spec['item_default'] + else: + return None + except isc.cc.data.DataNotFoundError as dnfe: + return None + + def get_value(self, identifier): + """Returns a tuple containing value,status. Status is either + LOCAL, CURRENT, DEFAULT or NONE, corresponding to the + source of the value""" + value = self.get_local_value(identifier) + if value: + return value, self.LOCAL + value = self.get_current_value(identifier) + if value: + return value, self.CURRENT + value = self.get_default_value(identifier) + if value: + return value, self.DEFAULT + return None, self.NONE + + def get_value_maps(self, identifier = None): + """Returns a list of dicts, containing the following values: + name: name of the entry (string) + type: string containing the type of the value (or 'module') + value: value of the entry if it is a string, int, double or bool + modified: true if the value is a local change + default: true if the value has been changed + Throws DataNotFoundError if the identifier is bad + TODO: use the consts for those last ones + """ + result = [] + if not identifier: + # No identifier, so we need the list of current modules + for module in self._specifications.keys(): + entry = {} + entry['name'] = module + entry['type'] = 'module' + entry['value'] = None + entry['modified'] = False + entry['default'] = False + result.append(entry) + else: + if identifier[0] == '/': + identifier = identifier[1:] + module, sep, id = identifier.partition('/') + spec = self.get_specification(module) + if spec: + print("[XX] getpartspec") + spec_part = find_spec(spec.get_config_data(), id) + print(spec_part) + if type(spec_part) == list: + for item in spec_part: + entry = {} + entry['name'] = item['item_name'] + entry['type'] = item['item_type'] + print("[XX] getvalue") + value, status = self.get_value("/" + identifier + "/" + item['item_name']) + entry['value'] = value + if status == self.LOCAL: + entry['modified'] = True + else: + entry['modified'] = False + if status == self.DEFAULT: + entry['default'] = False + else: + entry['default'] = False + result.append(entry) + else: + item = spec_part + if item['item_type'] == 'list': + li_spec = item['list_item_spec'] + print("[XX] item:") + print(item) + l, status = self.get_value("/" + identifier) + if l: + for value in l: + result_part2 = {} + result_part2['name'] = li_spec['item_name'] + result_part2['value'] = value + result_part2['type'] = li_spec['item_type'] + result_part2['default'] = False + result_part2['modified'] = False + result.append(result_part2) + else: + entry = {} + entry['name'] = item['item_name'] + entry['type'] = item['item_type'] + #value, status = self.get_value("/" + identifier + "/" + item['item_name']) + value, status = self.get_value("/" + identifier) + entry['value'] = value + if status == self.LOCAL: + entry['modified'] = True + else: + entry['modified'] = False + if status == self.DEFAULT: + entry['default'] = False + else: + entry['default'] = False + result.append(entry) + #print(spec) + return result + + def set_value(self, identifier, value): + """Set the local value at the given identifier to value""" + # todo: validate + isc.cc.data.set(self._local_changes, identifier, value) + + def get_config_item_list(self, identifier = None): + """Returns a list of strings containing the item_names of + the child items at the given identifier. If no identifier is + specified, returns a list of module names. The first part of + the identifier is interpreted as the module name""" + if identifier: + spec = self.find_spec_part(identifier) + return spec_name_list(spec, identifier + "/") + else: + return self._specifications.keys() + + +class UIConfigData(): + """This class is used in a configuration user interface. It contains + specific functions for getting, displaying, and sending + configuration settings.""" + def __init__(self, conn): + self._conn = conn + self._data = MultiConfigData() + self.request_specifications() + self.request_current_config() + a,b = self._data.get_value("/Boss/some_string") + print("[XX] a,b: " + str(a) + ", " + str(b)) + + def request_specifications(self): + # this step should be unnecessary but is the current way cmdctl returns stuff + # so changes are needed there to make this clean (we need a command to simply get the + # full specs for everything, including commands etc, not separate gets for that) + specs = self._conn.send_GET('/config_spec') + commands = self._conn.send_GET('/commands') + #print(specs) + #print(commands) + for module in specs.keys(): + cur_spec = { 'module_name': module } + if module in specs and specs[module]: + cur_spec['config_data'] = specs[module] + if module in commands and commands[module]: + cur_spec['commands'] = commands[module] + + self._data.set_specification(isc.config.DataDefinition(cur_spec)) + + def request_current_config(self): + config = self._conn.send_GET('/config_data') + if 'version' not in config or config['version'] != 1: + raise Exception("Bad config version") + self._data.set_current_config(config) + + def get_value(self, identifier): + return self._data.get_value(identifier) + + def set_value(self, identifier, value): + return self._data.set_value(identifier, value); + + def add_value(self, identifier, value_str): + data_spec = self._data.find_spec_part(identifier) + if (type(data_spec) != dict or "list_item_spec" not in data_spec): + raise DataTypeError(identifier + " is not a list") + value = isc.cc.data.parse_value_str(value_str) + cur_list, status = self.get_value(identifier) + if not cur_list: + cur_list = [] + if value not in cur_list: + cur_list.append(value) + self.set_value(identifier, cur_list) + + def remove_value(self, identifier, value_str): + data_spec = find_spec(self.config.specification, identifier) + if (type(data_spec) != dict or "list_item_spec" not in data_spec): + raise DataTypeError(identifier + " is not a list") + value = parse_value_str(value_str) + check_type(data_spec, [value]) + cur_list = find_no_exc(self.config_changes, identifier) + if not cur_list: + cur_list = find_no_exc(self.config.data, identifier) + if not cur_list: + cur_list = [] + if value in cur_list: + cur_list.remove(value) + set(self.config_changes, identifier, cur_list) + + def get_value_maps(self, identifier = None): + return self._data.get_value_maps(identifier) + + def commit(self): + self._conn.send_POST('/ConfigManager/set_config', self._data.get_local_changes()) + # todo: check result + self.request_current_config() + self._data.clear_local_changes() + + def get_config_item_list(self, identifier = None): + return self._data.get_config_item_list(identifier) + +# remove +class OUIConfigData(): + """This class is used in a configuration user interface. It contains + specific functions for getting, displaying, and sending + configuration settings.""" + def __init__(self, conn): + # the specs dict contains module: configdata elements + # these should all be replaced by the new stuff + data_spec = self.get_data_specification(conn) + self.config = data_spec + self.get_config_data(conn) + self.config_changes = {} + # + self.config_ + self.specs = self.get_data_specifications(conn) + + + def get_config_data(self, conn): + data = conn.send_GET('/config_data') + + def send_changes(self, conn): + conn.send_POST('/ConfigManager/set_config', self.config_changes) + # Get latest config data + self.get_config_data(conn) + self.config_changes = {} + + def get_data_specification(self, conn): + return conn.send_GET('/config_spec') + + def get_data_specifications(self, conn): + specs = {} + allspecs = conn.send_GET('/config_spec') + print("[XX] allspecs:") + print(allspecs) + + + def set(self, identifier, value): + # check against definition + spec = find_spec(identifier) + check_type(spec, value) + set(self.config_changes, identifier, value) + + def get_value(self, identifier): + """Returns a three-tuple, where the first item is the value + (or None), the second is a boolean specifying whether + the value is the default value, and the third is a boolean + specifying whether the value is an uncommitted change""" + value = isc.cc.data.find_no_exc(self.config_changes, identifier) + if value: + return value, False, True + value, default = self.config.get_value(identifier) + if value: + return value, default, False + return None, False, False + + def get_value_map_single(self, identifier, entry): + """Returns a single entry for a value_map, where the value is + not a part of a bigger map""" + result_part = {} + result_part['name'] = entry['item_name'] + result_part['type'] = entry['item_type'] + value, default, modified = self.get_value(identifier) + # should we check type and only set int, double, bool and string here? + result_part['value'] = value + result_part['default'] = default + result_part['modified'] = modified + return result_part + + def get_value_map(self, identifier, entry): + """Returns a single entry for a value_map, where the value is + a part of a bigger map""" + result_part = {} + result_part['name'] = entry['item_name'] + result_part['type'] = entry['item_type'] + value, default, modified = self.get_value(identifier + "/" + entry['item_name']) + # should we check type and only set int, double, bool and string here? + result_part['value'] = value + result_part['default'] = default + result_part['modified'] = modified + return result_part + + def get_value_maps(self, identifier = None): + """Returns a list of maps, containing the following values: + name: name of the entry (string) + type: string containing the type of the value (or 'module') + value: value of the entry if it is a string, int, double or bool + modified: true if the value is a local change + default: true if the value has been changed + Throws DataNotFoundError if the identifier is bad + """ + print("[XX] config:") + print(self.config) + spec = find_spec(self.config, identifier) + result = [] + if type(spec) == dict: + # either the top-level list of modules or a spec map + if 'item_name' in spec: + result_part = self.get_value_map_single(identifier, spec) + if result_part['type'] == "list": + values = self.get_value(identifier)[0] + if values: + for value in values: + result_part2 = {} + li_spec = spec['list_item_spec'] + result_part2['name'] = li_spec['item_name'] + result_part2['value'] = value + result_part2['type'] = li_spec['item_type'] + result_part2['default'] = False + result_part2['modified'] = False + result.append(result_part2) + else: + result.append(result_part) + + else: + for name in spec: + result_part = {} + result_part['name'] = name + result_part['type'] = "module" + result_part['value'] = None + result_part['default'] = False + result_part['modified'] = False + result.append(result_part) + elif type(spec) == list: + for entry in spec: + if type(entry) == dict and 'item_name' in entry: + result.append(self.get_value_map(identifier, entry)) + return result + + def add(self, identifier, value_str): + data_spec = find_spec(self.config.specification, identifier) + if (type(data_spec) != dict or "list_item_spec" not in data_spec): + raise DataTypeError(identifier + " is not a list") + value = parse_value_str(value_str) + check_type(data_spec, [value]) + cur_list = find_no_exc(self.config_changes, identifier) + if not cur_list: + cur_list = find_no_exc(self.config.data, identifier) + if not cur_list: + cur_list = [] + if value not in cur_list: + cur_list.append(value) + set(self.config_changes, identifier, cur_list) + + def remove(self, identifier, value_str): + data_spec = find_spec(self.config.specification, identifier) + if (type(data_spec) != dict or "list_item_spec" not in data_spec): + raise DataTypeError(identifier + " is not a list") + value = parse_value_str(value_str) + check_type(data_spec, [value]) + cur_list = find_no_exc(self.config_changes, identifier) + if not cur_list: + cur_list = find_no_exc(self.config.data, identifier) + if not cur_list: + cur_list = [] + if value in cur_list: + cur_list.remove(value) + set(self.config_changes, identifier, cur_list) + + def set(self, identifier, value_str): + data_spec = find_spec(self.config.specification, identifier) + value = parse_value_str(value_str) + check_type(data_spec, value) + set(self.config_changes, identifier, value) + + def unset(self, identifier): + # todo: check whether the value is optional? + unset(self.config_changes, identifier) + + def revert(self): + self.config_changes = {} + + def commit(self, conn): + self.send_changes(conn) diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py new file mode 100644 index 0000000000..5c22dcf821 --- /dev/null +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -0,0 +1,41 @@ +# Copyright (C) 2010 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. + +# +# Tests for the ConfigData and UIConfigData classes +# + +import unittest +import os +from isc.config.config_data import * +from isc.config.datadefinition import * + +class TestConfigData(unittest.TestCase): + def setUp(self): + if 'CONFIG_TESTDATA_PATH' in os.environ: + self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + else: + self.data_path = "../../../testdata" + + def test_data_spec_from_file(self): + spec = isc.config.data_spec_from_file(self.data_path + os.sep + "spec1.spec") + cd = ConfigData(spec) + self.assertEqual(cd.specification, spec) + self.assertEqual(cd.data, {}) + self.assertRaises(ConfigDataError, ConfigData, 1) + +if __name__ == '__main__': + unittest.main() + diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index e1f8f56c68..087c8970c6 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -27,16 +27,28 @@ import isc.cc.data class DataDefinitionError(Exception): pass +def data_spec_from_file(spec_file, check = True): + data_spec = None + if hasattr(spec_file, 'read'): + data_spec = ast.literal_eval(spec_file.read(-1)) + elif type(spec_file) == str: + file = open(spec_file) + data_spec = ast.literal_eval(file.read(-1)) + file.close() + else: + raise DataDefinitionError("spec_file not a str or file-like object") + if 'data_specification' not in data_spec: + raise DataDefinitionError("Data definition has no data_specification element") + + return DataDefinition(data_spec['data_specification'], check) + class DataDefinition: - def __init__(self, spec_file, check = True): - if hasattr(spec_file, 'read'): - self._data_spec = self.__read_data_spec_file(spec_file) - elif type(spec_file) == str: - file = open(spec_file) - self._data_spec = self.__read_data_spec_file(file) - file.close() - else: - raise DataDefinitionError("Not a str or file-like object") + def __init__(self, data_spec, check = True): + if type(data_spec) != dict: + raise DataDefinitionError("data_spec is of type " + str(type(data_spec)) + ", not dict") + if check: + _check(data_spec) + self._data_spec = data_spec def validate(self, data, errors = None): """Check whether the given piece of data conforms to this @@ -46,11 +58,6 @@ class DataDefinition: version stops as soon as there is one error so this list will not be exhaustive.""" data_def = self.get_definition() - if 'data_specification' not in data_def: - if errors: - errors.append("Data definition has no data_specification element") - return False - data_def = data_def['data_specification'] if 'config_data' not in data_def: if errors: errors.append("The is no config_data for this specification") @@ -58,26 +65,33 @@ class DataDefinition: errors = [] return _validate_spec_list(data_def['config_data'], data, errors) - def __read_data_spec_file(self, file, check = True): - """Reads the data spec from the given file object. - If check is True, check whether it is of the correct form. - If it is not, an DataDefinitionError exception is raised""" - if not hasattr(file, 'read'): - raise DataDefinitionError("Not a file-like object:" + str(type(file))) - str = file.read(-1) - # TODO catch error here and reraise as a less ugly exception - data_spec = ast.literal_eval(str) - if check: - # TODO - _check(data_spec) - pass - return data_spec + + def get_module_name(self): + return self._data_spec['module_name'] def get_definition(self): return self._data_spec - def get_module_name(self): - return self._data_spec["data_specification"]["module_name"] + def get_config_spec(self): + if 'config_data' in self._data_spec: + return self._data_spec['config_data'] + else: + return None + + def get_commands(self): + if 'commands' in self._data_spec: + return self._data_spec['commands'] + else: + return None + + def get_config_data(self): + if 'config_data' in self._data_spec: + return self._data_spec['config_data'] + else: + return None + + def __str__(self): + return self._data_spec.__str__() def _check(data_spec): """Checks the full specification. This is a dict that contains the @@ -87,9 +101,6 @@ def _check(data_spec): of dicts. Raises a DataDefinitionError if there is a problem.""" if type(data_spec) != dict: raise DataDefinitionError("data specification not a dict") - if "data_specification" not in data_spec: - raise DataDefinitionError("no data_specification element in specification") - data_spec = data_spec["data_specification"] if "module_name" not in data_spec: raise DataDefinitionError("no module_name in data_specification") if "config_data" in data_spec: @@ -105,7 +116,7 @@ def _check_config_spec(config_data): specification. Raises a DataDefinitionError if there is a problem.""" if type(config_data) != list: - raise DataDefinitionError("config_data is not a list of items") + raise DataDefinitionError("config_data is of type " + str(type(config_data)) + ", not a list of items") for config_item in config_data: _check_item_spec(config_item) diff --git a/src/lib/config/python/isc/config/datadefinition_test.py b/src/lib/config/python/isc/config/datadefinition_test.py index ead57fde72..da2bc1b5d8 100644 --- a/src/lib/config/python/isc/config/datadefinition_test.py +++ b/src/lib/config/python/isc/config/datadefinition_test.py @@ -25,29 +25,29 @@ import isc.cc.data class TestDataDefinition(unittest.TestCase): def setUp(self): - self.assert_('CONFIG_TESTDATA_PATH' in os.environ) - self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + if 'CONFIG_TESTDATA_PATH' in os.environ: + self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + else: + self.data_path = "../../../testdata" def spec_file(self, filename): return(self.data_path + os.sep + filename) def read_spec_file(self, filename): - return DataDefinition(self.spec_file(filename)) + return isc.config.data_spec_from_file(self.spec_file(filename)) def spec1(self, dd): - data_def = dd.get_definition() - self.assert_('data_specification' in data_def) - data_spec = data_def['data_specification'] + data_spec = dd.get_definition() self.assert_('module_name' in data_spec) self.assertEqual(data_spec['module_name'], "Spec1") def test_open_file_name(self): - dd = DataDefinition(self.spec_file("spec1.spec")) + dd = self.read_spec_file("spec1.spec") self.spec1(dd) def test_open_file_obj(self): file1 = open(self.spec_file("spec1.spec")) - dd = DataDefinition(file1) + dd = isc.config.data_spec_from_file(file1) self.spec1(dd) def test_bad_specfiles(self): @@ -72,7 +72,7 @@ class TestDataDefinition(unittest.TestCase): self.assertRaises(DataDefinitionError, self.read_spec_file, "spec21.spec") def validate_data(self, specfile_name, datafile_name): - dd = DataDefinition(self.spec_file(specfile_name)); + dd = self.read_spec_file(specfile_name); data_file = open(self.spec_file(datafile_name)) data_str = data_file.read() data = isc.cc.data.parse_value_str(data_str) @@ -89,7 +89,4 @@ class TestDataDefinition(unittest.TestCase): self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data")) if __name__ == '__main__': - if not 'CONFIG_TESTDATA_PATH' in os.environ: - print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files") - exit(1) unittest.main() diff --git a/src/lib/config/testdata/spec2.spec b/src/lib/config/testdata/spec2.spec index e6fdc0c6db..73388637db 100644 --- a/src/lib/config/testdata/spec2.spec +++ b/src/lib/config/testdata/spec2.spec @@ -49,6 +49,23 @@ } ] } + ], + "commands": [ + { + "command_name": "print_message", + "command_description": "Print the given message to stdout", + "command_args": [ { + "item_name": "message", + "item_type": "string", + "item_optional": False, + "item_default": "" + } ] + }, + { + "command_name": "shutdown", + "command_description": "Shut down BIND 10", + "command_args": [] + } ] } } diff --git a/src/lib/config/testdata/spec3.spec b/src/lib/config/testdata/spec3.spec index f5783963f9..f6a6ba08cf 100644 --- a/src/lib/config/testdata/spec3.spec +++ b/src/lib/config/testdata/spec3.spec @@ -1,6 +1,6 @@ { "data_specification": { - "module_name": "Spec2", + "module_name": "Spec3", "config_data": [ { "item_type": "integer", From bc69156fbd4d96708a1777660cbb142d3bea7fb0 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 13:30:39 +0000 Subject: [PATCH 16/81] added option to validate 'partial' data against a definition added some more dummy specfile entries creates two simple functions for making and reading answer messages bindctl will parse command 'config set' values natively (i.e. for "config set my_item 3" the 3 is now read as an integer instead of a string) added config diff option that shows a dict of the current uncommited changes git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@823 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth.spec | 17 ++++ src/bin/bind10/bind10.py.in | 20 +++- src/bin/bind10/bob.spec | 16 ++- src/bin/bindctl/bindcmd.py | 11 ++- src/bin/bindctl/bindctl.py | 3 + src/bin/cmdctl/b10-cmdctl.py.in | 4 +- src/lib/config/python/isc/config/ccsession.py | 98 ++++++++++++++----- src/lib/config/python/isc/config/cfgmgr.py | 39 ++++---- .../config/python/isc/config/cfgmgr_test.py | 18 ---- .../config/python/isc/config/config_data.py | 24 ++++- .../python/isc/config/datadefinition.py | 19 ++-- 11 files changed, 194 insertions(+), 75 deletions(-) diff --git a/src/bin/auth/auth.spec b/src/bin/auth/auth.spec index f3b4b7f300..ad4b45e8e6 100644 --- a/src/bin/auth/auth.spec +++ b/src/bin/auth/auth.spec @@ -18,6 +18,23 @@ "item_default": "" } } + ], + "commands": [ + { + "command_name": "print_message", + "command_description": "Print the given message to stdout", + "command_args": [ { + "item_name": "message", + "item_type": "string", + "item_optional": False, + "item_default": "" + } ] + }, + { + "command_name": "shutdown", + "command_description": "Shut down BIND 10", + "command_args": [] + } ] } } diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index adf265b61a..b17d8fa8ac 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -106,6 +106,7 @@ class BoB: self.verbose = verbose self.c_channel_port = c_channel_port self.cc_session = None + self.ccs = None self.processes = {} self.dead_processes = {} self.runnable = False @@ -114,6 +115,18 @@ class BoB: if self.verbose: print("[XX] handling new config:") print(new_config) + errors = [] + if self.ccs.get_config_data().get_specification().validate(False, new_config, errors): + print("[XX] new config validated") + self.ccs.set_config(new_config) + answer = { "result": [ 0 ] } + else: + print("[XX] new config validation failure") + if len(errors) > 0: + answer = { "result": [ 1, errors ] } + else: + answer = { "result": [ 1, "Unknown error in validation" ] } + return answer # TODO def command_handler(self, command): @@ -121,7 +134,7 @@ class BoB: if self.verbose: print("[XX] Boss got command:") print(command) - answer = None + answer = [ 1, "Command not implemented" ] if type(command) != list or len(command) == 0: answer = { "result": [ 1, "bad command" ] } else: @@ -134,6 +147,10 @@ class BoB: if len(command) > 1 and type(command[1]) == dict and "message" in command[1]: print(command[1]["message"]) answer = { "result": [ 0 ] } + elif cmd == "print_settings": + print("Config:") + print(self.ccs.get_config()) + answer = { "result": [ 0 ] } else: answer = { "result": [ 1, "Unknown command" ] } return answer @@ -191,6 +208,7 @@ class BoB: if self.verbose: print("[XX] starting ccsession") self.ccs = isc.config.CCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler) + self.ccs.start() if self.verbose: print("[XX] ccsession started") diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec index 230c35e0ba..71043ebd9a 100644 --- a/src/bin/bind10/bob.spec +++ b/src/bin/bind10/bob.spec @@ -9,10 +9,10 @@ "item_default": "Hi, shane!" }, { - "item_name": "some_other_string", - "item_type": "string", + "item_name": "some_int", + "item_type": "integer", "item_optional": False, - "item_default": "Hi, shane!" + "item_default": 1 } ], "commands": [ @@ -26,6 +26,16 @@ "item_default": "" } ] }, + { + "command_name": "print_settings", + "command_description": "Print some_string and some_int to stdout", + "command_args": [ { + "item_name": "message", + "item_type": "string", + "item_optional": True, + "item_default": "" + } ] + }, { "command_name": "shutdown", "command_description": "Shut down BIND 10", diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index 908d081bad..99399c2c77 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -31,6 +31,7 @@ import os, time, random, re import getpass from hashlib import sha1 import csv +import ast try: from collections import OrderedDict @@ -445,13 +446,21 @@ class BindCmdInterpreter(Cmd): elif cmd.command == "remove": self.config_data.remove_value(identifier, cmd.params['value']) elif cmd.command == "set": - self.config_data.set_value(identifier, cmd.params['value']) + parsed_value = None + try: + parsed_value = ast.literal_eval(cmd.params['value']) + except Exception as exc: + # ok could be an unquoted string, interpret as such + parsed_value = cmd.params['value'] + self.config_data.set_value(identifier, parsed_value) elif cmd.command == "unset": self.config_data.unset(identifier) elif cmd.command == "revert": self.config_data.revert() elif cmd.command == "commit": self.config_data.commit() + elif cmd.command == "diff": + print(self.config_data.get_local_changes()); elif cmd.command == "go": self.go(identifier) except isc.cc.data.DataTypeError as dte: diff --git a/src/bin/bindctl/bindctl.py b/src/bin/bindctl/bindctl.py index 162177cb64..6e5715efed 100644 --- a/src/bin/bindctl/bindctl.py +++ b/src/bin/bindctl/bindctl.py @@ -53,6 +53,9 @@ def prepare_config_commands(tool): cmd.add_param(param) module.add_command(cmd) + cmd = CommandInfo(name = "diff", desc = "Show all local changes", need_inst_param = False) + module.add_command(cmd) + cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False) module.add_command(cmd) diff --git a/src/bin/cmdctl/b10-cmdctl.py.in b/src/bin/cmdctl/b10-cmdctl.py.in index 9737d5fa3f..2cffa90d80 100644 --- a/src/bin/cmdctl/b10-cmdctl.py.in +++ b/src/bin/cmdctl/b10-cmdctl.py.in @@ -168,8 +168,8 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): param = json.loads(post_str) # TODO, need return some proper return code. # currently always OK. - reply = self.server.send_command_to_module(mod, cmd, param) - print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod)) + reply = self.server.send_command_to_module(mod, cmd, param) + print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod)) return rcode, reply diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 358dfc0686..0b46443857 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -25,10 +25,45 @@ from isc.cc import Session import isc +class CCSessionError(Exception): pass + +def parse_answer(msg): + """Returns a type (rcode, value), where value depends on the command + that was called. If rcode != 0, value is a string containing + an error message""" + if 'result' not in msg: + raise CCSessionError("answer message does not contain 'result' element") + elif type(msg['result']) != list: + raise CCSessionError("wrong result type in answer message") + elif len(msg['result']) < 1: + raise CCSessionError("empty result list in answer message") + elif type(msg['result'][0]) != int: + raise CCSessionError("wrong rcode type in answer message") + else: + if len(msg['result']) > 1: + return msg['result'][0], msg['result'][1] + else: + return msg['result'][0], None + +def create_answer(rcode, arg = None): + """Creates an answer packet for config&commands. rcode must be an + integer. If rcode == 0, arg is an optional value that depends + on what the command or option was. If rcode != 0, arg must be + a string containing an error message""" + if type(rcode) != int: + raise CCSessionError("rcode in create_answer() must be an integer") + if rcode != 0 and type(arg) != str: + raise CCSessionError("arg in create_answer for rcode != 0 must be a string describing the error") + if arg: + return { 'result': [ rcode, arg ] } + else: + return { 'result': [ 0 ] } + class CCSession: def __init__(self, spec_file_name, config_handler, command_handler): - self._data_definition = isc.config.data_spec_from_file(spec_file_name) - self._module_name = self._data_definition.get_module_name() + data_definition = isc.config.data_spec_from_file(spec_file_name) + self._config_data = isc.config.config_data.ConfigData(data_definition) + self._module_name = data_definition.get_module_name() self.set_config_handler(config_handler) self.set_command_handler(command_handler) @@ -36,8 +71,10 @@ class CCSession: self._session = Session() self._session.group_subscribe(self._module_name, "*") + def start(self): + print("[XX] SEND SPEC AND REQ CONFIG") self.__send_spec() - self.__get_full_config() + self.__request_config() def get_socket(self): """Returns the socket from the command channel session""" @@ -48,6 +85,15 @@ class CCSession: application can use it directly""" return self._session + def set_config(self, new_config): + return self._config_data.set_local_config(new_config) + + def get_config(self): + return self._config_data.get_local_config() + + def get_config_data(self): + return self._config_data + def close(self): self._session.close() @@ -55,13 +101,18 @@ class CCSession: """Check whether there is a command on the channel. Call the command callback function if so""" msg, env = self._session.group_recvmsg(False) + # should we default to an answer? success-by-default? unhandled error? answer = None - if msg: - if "config_update" in msg and self._config_handler: - self._config_handler(msg["config_update"]) - answer = { "result": [ 0 ] } - if "command" in msg and self._command_handler: - answer = self._command_handler(msg["command"]) + try: + if msg: + print("[XX] got msg: ") + print(msg) + if "config_update" in msg and self._config_handler: + answer = self._config_handler(msg["config_update"]) + if "command" in msg and self._command_handler: + answer = self._command_handler(msg["command"]) + except Exception as exc: + answer = create_answer(1, str(exc)) if answer: self._session.group_reply(env, answer) @@ -69,32 +120,35 @@ class CCSession: def set_config_handler(self, config_handler): """Set the config handler for this module. The handler is a function that takes the full configuration and handles it. - It should return either { "result": [ 0 ] } or - { "result": [ , "error message" ] }""" + It should return an answer created with create_answer()""" self._config_handler = config_handler # should we run this right now since we've changed the handler? def set_command_handler(self, command_handler): """Set the command handler for this module. The handler is a function that takes a command as defined in the .spec file - and return either { "result": [ 0, (result) ] } or - { "result": [ . "error message" ] }""" + and return an answer created with create_answer()""" self._command_handler = command_handler def __send_spec(self): """Sends the data specification to the configuration manager""" - self._session.group_sendmsg({ "data_specification": self._data_definition.get_definition() }, "ConfigManager") + print("[XX] send spec for " + self._module_name + " to ConfigManager") + self._session.group_sendmsg({ "data_specification": self._config_data.get_specification().get_definition() }, "ConfigManager") answer, env = self._session.group_recvmsg(False) + print("[XX] got answer from cfgmgr:") + print(answer) - def __get_full_config(self): + def __request_config(self): """Asks the configuration manager for the current configuration, and call the config handler if set""" self._session.group_sendmsg({ "command": [ "get_config", { "module_name": self._module_name } ] }, "ConfigManager") answer, env = self._session.group_recvmsg(False) - if "result" in answer: - if answer["result"][0] == 0 and len(answer["result"]) > 1: - new_config = answer["result"][1] - if self._data_definition.validate(new_config): - self._config = new_config; - if self._config_handler: - self._config_handler(answer["result"]) + rcode, value = parse_answer(answer) + if rcode == 0: + if self._config_data.get_specification().validate(False, value): + self._config_data.set_local_config(value); + if self._config_handler: + self._config_handler(value) + else: + # log error + print("Error requesting configuration: " + value) diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 6b696008f5..9d2498cf62 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -156,7 +156,6 @@ class ConfigManager: commands[name] = self.data_specs[name].get_commands else: for module_name in self.data_specs.keys(): - print("[XX] add commands for " + module_name) commands[module_name] = self.data_specs[module_name].get_commands() return commands @@ -218,24 +217,34 @@ class ConfigManager: if conf_part: data.merge(conf_part, cmd[2]) self.cc.group_sendmsg({ "config_update": conf_part }, module_name) + answer, env = self.cc.group_recvmsg(False) else: conf_part = data.set(self.config.data, module_name, {}) - print("[XX] SET CONF PART:") - print(conf_part) data.merge(conf_part[module_name], cmd[2]) # send out changed info self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) - self.write_config() - answer["result"] = [ 0 ] + # replace 'our' answer with that of the module + answer, env = selc.cc.group_recvmsg(False) + print("[XX] module responded with") + print(answer) + if answer and "result" in answer and answer['result'][0] == 0: + self.write_config() elif len(cmd) == 2: # todo: use api (and check the data against the definition?) data.merge(self.config.data, cmd[1]) # send out changed info + got_error = False for module in self.config.data: if module != "version": self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) - self.write_config() - answer["result"] = [ 0 ] + answer, env = self.cc.group_recvmsg(False) + print("[XX] one module responded with") + print(answer) + if answer and 'result' in answer and answer['result'][0] != 0: + got_error = True + if not got_error: + self.write_config() + # TODO rollback changes that did get through? else: answer["result"] = [ 1, "Wrong number of arguments" ] return answer @@ -245,9 +254,9 @@ class ConfigManager: # todo: use DataDefinition class # todo: error checking (like keyerrors) answer = {} - print("[XX] CFGMGR got spec:") - print(spec) self.set_data_spec(spec) + print("[XX] cfgmgr add spec:") + print(spec) # We should make one general 'spec update for module' that # passes both specification and commands at once @@ -259,23 +268,15 @@ class ConfigManager: def handle_msg(self, msg): """Handle a direct command""" answer = {} - print("[XX] cfgmgr got msg:") - print(msg) if "command" in msg: cmd = msg["command"] try: if cmd[0] == "get_commands": answer["result"] = [ 0, self.get_commands() ] - print("[XX] get_commands answer:") - print(answer) elif cmd[0] == "get_data_spec": answer = self._handle_get_data_spec(cmd) - print("[XX] get_data_spec answer:") - print(answer) elif cmd[0] == "get_config": answer = self._handle_get_config(cmd) - print("[XX] get_config answer:") - print(answer) elif cmd[0] == "set_config": answer = self._handle_set_config(cmd) elif cmd[0] == "shutdown": @@ -297,8 +298,6 @@ class ConfigManager: answer['result'] = [0] else: answer["result"] = [ 1, "Unknown message format: " + str(msg) ] - print("[XX] cfgmgr sending answer:") - print(answer) return answer def run(self): @@ -307,6 +306,8 @@ class ConfigManager: msg, env = self.cc.group_recvmsg(False) if msg: answer = self.handle_msg(msg); + print("[XX] CFGMGR Sending answer to UI:") + print(answer) self.cc.group_reply(env, answer) else: self.running = False diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 8ab3443dcc..916a74c2f2 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -119,24 +119,6 @@ class TestConfigManager(unittest.TestCase): # this one is actually wrong, but 'current status quo' self.assertEqual(msg, {"running": "configmanager"}) - #def test_set_config(self): - #self.cm.set_config(self.name, self.spec) - #self.assertEqual(self.cm.data_definitions[self.name], self.spec) - - #def test_remove_config(self): - #self.assertRaises(KeyError, self.cm.remove_config, self.name) - #self.cm.set_config(self.name, self.spec) - #self.cm.remove_config(self.name) - - #def test_set_commands(self): - # self.cm.set_commands(self.name, self.commands) - # self.assertEqual(self.cm.commands[self.name], self.commands) - - #def test_write_config(self): - # self.assertRaises(KeyError, self.cm.remove_commands, self.name) - # self.cm.set_commands(self.name, self.commands) - # self.cm.remove_commands(self.name) - def _handle_msg_helper(self, msg, expected_answer): answer = self.cm.handle_msg(msg) self.assertEqual(expected_answer, answer) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index be438a3375..0664f5551d 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -137,6 +137,25 @@ class ConfigData: return spec['item_default'], True return None, False + def get_specification(self): + """Returns the datadefinition""" + print(self.specification) + return self.specification + + def set_local_config(self, data): + """Set the non-default config values, as passed by cfgmgr""" + self.data = data + + def get_local_config(self): + """Returns the non-default config values in a dict""" + return self.config(); + + #def get_identifiers(self): + # Returns a list containing all identifiers + + #def + + class MultiConfigData: """This class stores the datadefinitions, current non-default configuration values and 'local' (uncommitted) changes.""" @@ -308,7 +327,7 @@ class MultiConfigData: """Set the local value at the given identifier to value""" # todo: validate isc.cc.data.set(self._local_changes, identifier, value) - + def get_config_item_list(self, identifier = None): """Returns a list of strings containing the item_names of the child items at the given identifier. If no identifier is @@ -392,6 +411,9 @@ class UIConfigData(): def get_value_maps(self, identifier = None): return self._data.get_value_maps(identifier) + def get_local_changes(self): + return self._data.get_local_changes() + def commit(self): self._conn.send_POST('/ConfigManager/set_config', self._data.get_local_changes()) # todo: check result diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index 087c8970c6..a7391b56fc 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -50,20 +50,23 @@ class DataDefinition: _check(data_spec) self._data_spec = data_spec - def validate(self, data, errors = None): + def validate(self, full, data, errors = None): """Check whether the given piece of data conforms to this data definition. If so, it returns True. If not, it will return false. If errors is given, and is an array, a string describing the error will be appended to it. The current version stops as soon as there is one error so this list - will not be exhaustive.""" + will not be exhaustive. If 'full' is true, it also errors on + non-optional missing values. Set this to False if you want to + validate only a part of a configuration tree (like a list of + non-default values)""" data_def = self.get_definition() if 'config_data' not in data_def: if errors: errors.append("The is no config_data for this specification") return False errors = [] - return _validate_spec_list(data_def['config_data'], data, errors) + return _validate_spec_list(data_def['config_data'], full, data, errors) def get_module_name(self): @@ -89,7 +92,7 @@ class DataDefinition: return self._data_spec['config_data'] else: return None - + def __str__(self): return self._data_spec.__str__() @@ -246,21 +249,21 @@ def _validate_item(spec, data, errors): return False return True -def _validate_spec(spec, data, errors): +def _validate_spec(spec, full, data, errors): item_name = spec['item_name'] item_optional = spec['item_optional'] if item_name in data: return _validate_item(spec, data[item_name], errors) - elif not item_optional: + elif full and not item_optional: if errors: errors.append("non-optional item " + item_name + " missing") return False else: return True -def _validate_spec_list(data_spec, data, errors): +def _validate_spec_list(data_spec, full, data, errors): for spec_item in data_spec: - if not _validate_spec(spec_item, data, errors): + if not _validate_spec(spec_item, full, data, errors): return False return True From 51512f41d11266181093232cfc8182b53407d5fc Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 14:00:46 +0000 Subject: [PATCH 17/81] use functions to create answer messages in cfgmgr git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@824 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/cfgmgr.py | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 9d2498cf62..c79131e823 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -179,13 +179,13 @@ class ConfigManager: if type(cmd[1]) == dict: if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': module_name = cmd[1]['module_name'] - answer["result"] = [0, self.get_config_data(module_name)] + answer = isc.config.ccsession.create_answer(0, self.get_config_data(module_name)) else: - answer["result"] = [1, "Bad module_name in get_data_spec command"] + answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_data_spec command") else: - answer["result"] = [1, "Bad get_data_spec command, argument not a dict"] + answer = isc.config.ccsession.create_answer(1, "Bad get_data_spec command, argument not a dict") else: - answer["result"] = [0, self.get_config_data()] + answer = isc.config.ccsession.create_answer(0, self.get_config_data()) return answer def _handle_get_config(self, cmd): @@ -195,17 +195,17 @@ class ConfigManager: if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': module_name = cmd[1]['module_name'] try: - answer["result"] = [0, data.find(self.config.data, module_name) ] + answer = isc.config.ccsession.create_answer(0, data.find(self.config.data, module_name)) except data.DataNotFoundError as dnfe: # no data is ok, that means we have nothing that # deviates from default values - answer["result"] = [0, {} ] + answer = isc.config.ccsession.create_answer(0, {}) else: - answer["result"] = [1, "Bad module_name in get_config command"] + answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_config command") else: - answer["result"] = [1, "Bad get_config command, argument not a dict"] + answer = isc.config.ccsession.create_answer(1, "Bad get_config command, argument not a dict") else: - answer["result"] = [0, self.config.data] + answer = isc.config.ccsession.create_answer(0, self.config.data) return answer def _handle_set_config(self, cmd): @@ -227,7 +227,8 @@ class ConfigManager: answer, env = selc.cc.group_recvmsg(False) print("[XX] module responded with") print(answer) - if answer and "result" in answer and answer['result'][0] == 0: + rcode, val = isc.config.ccsession.parse_answer(answer) + if rcode == 0: self.write_config() elif len(cmd) == 2: # todo: use api (and check the data against the definition?) @@ -240,13 +241,15 @@ class ConfigManager: answer, env = self.cc.group_recvmsg(False) print("[XX] one module responded with") print(answer) - if answer and 'result' in answer and answer['result'][0] != 0: + rcode, val = isc.config.ccsession.parse_answer(answer) + if rcode != 0: got_error = True if not got_error: self.write_config() # TODO rollback changes that did get through? + # feed back *all* errors? else: - answer["result"] = [ 1, "Wrong number of arguments" ] + answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") return answer def _handle_data_specification(self, spec): @@ -262,7 +265,7 @@ class ConfigManager: # passes both specification and commands at once self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_data() ] }, "Cmd-Ctrld") self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands() ] }, "Cmd-Ctrld") - answer["result"] = [ 0 ] + answer = isc.config.ccsession.create_answer(0) return answer def handle_msg(self, msg): @@ -272,7 +275,7 @@ class ConfigManager: cmd = msg["command"] try: if cmd[0] == "get_commands": - answer["result"] = [ 0, self.get_commands() ] + answer = isc.config.ccsession.create_answer(0, self.get_commands()) elif cmd[0] == "get_data_spec": answer = self._handle_get_data_spec(cmd) elif cmd[0] == "get_config": @@ -282,22 +285,22 @@ class ConfigManager: elif cmd[0] == "shutdown": print("[bind-cfgd] Received shutdown command") self.running = False - answer["result"] = [ 0 ] + answer = isc.config.ccsession.create_answer(0) else: - answer["result"] = [ 1, "Unknown command: " + str(cmd) ] + answer = isc.config.ccsession.create_answer(1, "Unknown command: " + str(cmd)) except IndexError as ie: - answer["result"] = [ 1, "Missing argument in command: " + str(ie) ] + answer = isc.config.ccsession.create_answer(1, "Missing argument in command: " + str(ie)) raise ie elif "data_specification" in msg: try: answer = self._handle_data_specification(isc.config.DataDefinition(msg["data_specification"])) except isc.config.DataDefinitionError as dde: - answer['result'] = [ 1, "Error in data definition: " + str(dde) ] + answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde)) elif 'result' in msg: # this seems wrong, might start pingpong - answer['result'] = [0] + answer = isc.config.ccsession.create_answer(0) else: - answer["result"] = [ 1, "Unknown message format: " + str(msg) ] + answer = isc.config.ccsession.create_answer(1, "Unknown message format: " + str(msg)) return answer def run(self): @@ -308,6 +311,8 @@ class ConfigManager: answer = self.handle_msg(msg); print("[XX] CFGMGR Sending answer to UI:") print(answer) + print("For command") + print(msg) self.cc.group_reply(env, answer) else: self.running = False From ffe26df93c0a0d46f6911719031ecfe136a5265d Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 14:10:30 +0000 Subject: [PATCH 18/81] same for bob git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@825 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10.py.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index b17d8fa8ac..23633c8b6f 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -119,13 +119,13 @@ class BoB: if self.ccs.get_config_data().get_specification().validate(False, new_config, errors): print("[XX] new config validated") self.ccs.set_config(new_config) - answer = { "result": [ 0 ] } + answer = isc.config.ccsession.create_answer(0) else: print("[XX] new config validation failure") if len(errors) > 0: - answer = { "result": [ 1, errors ] } + answer = isc.config.ccsession.create_answer(1, " ".join(errors)) else: - answer = { "result": [ 1, "Unknown error in validation" ] } + answer = isc.config.ccsession.create_answer(1, "Unknown error in validation") return answer # TODO @@ -136,23 +136,23 @@ class BoB: print(command) answer = [ 1, "Command not implemented" ] if type(command) != list or len(command) == 0: - answer = { "result": [ 1, "bad command" ] } + answer = isc.config.ccsession.create_answer(1, "bad command") else: cmd = command[0] if cmd == "shutdown": print("[XX] got shutdown command") self.runnable = False - answer = { "result": [ 0 ] } + answer = isc.config.ccsession.create_answer(0) elif cmd == "print_message": if len(command) > 1 and type(command[1]) == dict and "message" in command[1]: print(command[1]["message"]) - answer = { "result": [ 0 ] } + answer = isc.config.ccsession.create_answer(0) elif cmd == "print_settings": print("Config:") print(self.ccs.get_config()) - answer = { "result": [ 0 ] } + answer = isc.config.ccsession.create_answer(0) else: - answer = { "result": [ 1, "Unknown command" ] } + answer = isc.config.ccsession.create_answer(1, "Unknown command") return answer def startup(self): From 7742c462391fe0c7f70698fabe680af6aa682caa Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 15:40:44 +0000 Subject: [PATCH 19/81] empty list evaluates to False, so check for None explicitely git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@826 e5f2f494-b856-4b98-b285-d166d9295462 --- .../config/python/isc/config/datadefinition.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index a7391b56fc..43b0005f77 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -65,7 +65,6 @@ class DataDefinition: if errors: errors.append("The is no config_data for this specification") return False - errors = [] return _validate_spec_list(data_def['config_data'], full, data, errors) @@ -207,27 +206,27 @@ def _validate_type(spec, value, errors): specification""" data_type = spec['item_type'] if data_type == "integer" and type(value) != int: - if errors: + if errors != None: errors.append(str(value) + " should be an integer") return False elif data_type == "real" and type(value) != float: - if errors: + if errors != None: errors.append(str(value) + " should be a real") return False elif data_type == "boolean" and type(value) != bool: - if errors: + if errors != None: errors.append(str(value) + " should be a boolean") return False elif data_type == "string" and type(value) != str: - if errors: + if errors != None: errors.append(str(value) + " should be a string") return False elif data_type == "list" and type(value) != list: - if errors: + if errors != None: errors.append(str(value) + " should be a list, not a " + str(value.__class__.__name__)) return False elif data_type == "map" and type(value) != dict: - if errors: + if errors != None: errors.append(str(value) + " should be a map") return False else: @@ -256,7 +255,7 @@ def _validate_spec(spec, full, data, errors): if item_name in data: return _validate_item(spec, data[item_name], errors) elif full and not item_optional: - if errors: + if errors != None: errors.append("non-optional item " + item_name + " missing") return False else: From fad19e22d4aa4a5a949d1453c70778979d2a6747 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 19:46:25 +0000 Subject: [PATCH 20/81] documentation git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@827 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 0b46443857..50895dc67b 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -21,6 +21,9 @@ # modeled after ccsession.h/cc 'protocol' changes here need to be # made there as well +"""This module provides the CCSession class, as well as a set of + utility functions to create and parse messages related to commands + and configuration""" from isc.cc import Session import isc @@ -28,9 +31,9 @@ import isc class CCSessionError(Exception): pass def parse_answer(msg): - """Returns a type (rcode, value), where value depends on the command - that was called. If rcode != 0, value is a string containing - an error message""" + """Returns a tuple (rcode, value), where value depends on the + command that was called. If rcode != 0, value is a string + containing an error message""" if 'result' not in msg: raise CCSessionError("answer message does not contain 'result' element") elif type(msg['result']) != list: @@ -60,7 +63,23 @@ def create_answer(rcode, arg = None): return { 'result': [ 0 ] } class CCSession: + """This class maintains a connection to the command channel, as + well as configuration options for modules. The module provides + a specification file that contains the module name, configuration + options, and commands. It also gives the CCSession two callback + functions, one to call when there is a direct command to the + module, and one to update the configuration run-time. These + callbacks are called when 'check_command' is called on the + CCSession""" + def __init__(self, spec_file_name, config_handler, command_handler): + """Initialize a CCSession. This does *NOT* send the + specification and request the configuration yet. Use start() + for that once the CCSession has been initialized. + specfile_name is the path to the specification file + config_handler and command_handler are callback functions, + see set_config_handler and set_command_handler for more + information on their signatures.""" data_definition = isc.config.data_spec_from_file(spec_file_name) self._config_data = isc.config.config_data.ConfigData(data_definition) self._module_name = data_definition.get_module_name() @@ -72,50 +91,59 @@ class CCSession: self._session.group_subscribe(self._module_name, "*") def start(self): - print("[XX] SEND SPEC AND REQ CONFIG") + """Send the specification for this module to the configuration + manager, and request the current non-default configuration. + The config_handler will be called with that configuration""" self.__send_spec() self.__request_config() def get_socket(self): - """Returns the socket from the command channel session""" + """Returns the socket from the command channel session. This can + be used in select() loops to see if there is anything on the + channel. This is not strictly necessary as long as + check_command is called periodically.""" return self._session._socket def get_session(self): """Returns the command-channel session that is used, so the - application can use it directly""" + application can use it directly.""" return self._session def set_config(self, new_config): + """Sets the current or non-default configuration""" return self._config_data.set_local_config(new_config) def get_config(self): + """Returns the current or non-default configuration""" return self._config_data.get_local_config() def get_config_data(self): + """Returns the config_data part of the specification""" return self._config_data def close(self): + """Close the session to the command channel""" self._session.close() def check_command(self): - """Check whether there is a command on the channel. - Call the command callback function if so""" + """Check whether there is a command or configuration update + on the channel. Call the corresponding callback function if + there is.""" msg, env = self._session.group_recvmsg(False) # should we default to an answer? success-by-default? unhandled error? - answer = None - try: - if msg: + if msg: + answer = None + try: print("[XX] got msg: ") print(msg) if "config_update" in msg and self._config_handler: answer = self._config_handler(msg["config_update"]) if "command" in msg and self._command_handler: answer = self._command_handler(msg["command"]) - except Exception as exc: - answer = create_answer(1, str(exc)) - if answer: - self._session.group_reply(env, answer) - + except Exception as exc: + answer = create_answer(1, str(exc)) + if answer: + self._session.group_reply(env, answer) def set_config_handler(self, config_handler): """Set the config handler for this module. The handler is a From ae08d2a7bb815226d4ed5c28cfd8b98ee165c332 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 15 Feb 2010 20:40:22 +0000 Subject: [PATCH 21/81] updated tests, fixed a few bugs, updated docs git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@828 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 4 ++-- src/lib/config/python/isc/config/cfgmgr.py | 16 ++++++++++++---- src/lib/config/python/isc/config/cfgmgr_test.py | 17 ++++++++++++++++- src/lib/config/python/isc/config/config_data.py | 12 ++++++++---- .../config/python/isc/config/datadefinition.py | 8 ++++---- .../python/isc/config/datadefinition_test.py | 2 +- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 50895dc67b..bfaf22c7d2 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -57,10 +57,10 @@ def create_answer(rcode, arg = None): raise CCSessionError("rcode in create_answer() must be an integer") if rcode != 0 and type(arg) != str: raise CCSessionError("arg in create_answer for rcode != 0 must be a string describing the error") - if arg: + if arg != None: return { 'result': [ rcode, arg ] } else: - return { 'result': [ 0 ] } + return { 'result': [ rcode ] } class CCSession: """This class maintains a connection to the command channel, as diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index c79131e823..eab3883892 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -209,7 +209,7 @@ class ConfigManager: return answer def _handle_set_config(self, cmd): - answer = {} + answer = None if len(cmd) == 3: # todo: use api (and check the data against the definition?) module_name = cmd[1] @@ -224,7 +224,7 @@ class ConfigManager: # send out changed info self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) # replace 'our' answer with that of the module - answer, env = selc.cc.group_recvmsg(False) + answer, env = self.cc.group_recvmsg(False) print("[XX] module responded with") print(answer) rcode, val = isc.config.ccsession.parse_answer(answer) @@ -235,6 +235,7 @@ class ConfigManager: data.merge(self.config.data, cmd[1]) # send out changed info got_error = False + err_list = [] for module in self.config.data: if module != "version": self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) @@ -244,12 +245,19 @@ class ConfigManager: rcode, val = isc.config.ccsession.parse_answer(answer) if rcode != 0: got_error = True + err_list.append(val) if not got_error: self.write_config() - # TODO rollback changes that did get through? - # feed back *all* errors? + answer = isc.config.ccsession.create_answer(0) + else: + # TODO rollback changes that did get through? + # feed back *all* errors? + answer = isc.config.ccsession.create_answer(1, " ".join(err_list)) else: answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") + if not answer: + answer = isc.config.ccsession.create_answer(1, "Error handling set_config command") + return answer def _handle_data_specification(self, spec): diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 916a74c2f2..909c9017ac 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -87,6 +87,13 @@ class FakeCCSession: def group_sendmsg(self, msg, channel, target = None): self.message_queue.append([ channel, target, msg ]) + def group_recvmsg(self, blocking): + for qm in self.message_queue: + if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in self.subscriptions[qm[0]]): + self.message_queue.remove(qm) + return qm[2], {} + return None, None + def get_message(self, channel, target = None): for qm in self.message_queue: if qm[0] == channel and qm[1] == target: @@ -147,13 +154,21 @@ class TestConfigManager(unittest.TestCase): self._handle_msg_helper({ "command": [ "set_config", {} ] }, {'result': [0]}) self.assertEqual(len(self.fake_session.message_queue), 0) + + # the targets of some of these tests expect specific answers, put + # those in our fake msgq first. + my_ok_answer = { 'result': [ 0 ] } + + self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 123 } ] }, - {'result': [0]}) + my_ok_answer) self.assertEqual(len(self.fake_session.message_queue), 1) + self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") self.assertEqual({'config_update': {'test': 123}}, self.fake_session.get_message(self.name, None)) self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 124 } ] }, {'result': [0]}) + #print(self.fake_session.message_queue) self.assertEqual(len(self.fake_session.message_queue), 1) self.assertEqual({'config_update': {'test': 124}}, diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 0664f5551d..d6aff78c09 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -158,7 +158,8 @@ class ConfigData: class MultiConfigData: """This class stores the datadefinitions, current non-default - configuration values and 'local' (uncommitted) changes.""" + configuration values and 'local' (uncommitted) changes for + multiple modules""" LOCAL = 1 CURRENT = 2 DEFAULT = 3 @@ -181,7 +182,8 @@ class MultiConfigData: return None def find_spec_part(self, identifier): - """returns the default value, or None if there is no default""" + """returns the specification for the item at the given + identifier, or None if not found""" if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition("/") @@ -229,7 +231,8 @@ class MultiConfigData: def get_value(self, identifier): """Returns a tuple containing value,status. Status is either LOCAL, CURRENT, DEFAULT or NONE, corresponding to the - source of the value""" + source of the value (local change, current setting, default + as specified by the specification, or not found at all).""" value = self.get_local_value(identifier) if value: return value, self.LOCAL @@ -332,7 +335,8 @@ class MultiConfigData: """Returns a list of strings containing the item_names of the child items at the given identifier. If no identifier is specified, returns a list of module names. The first part of - the identifier is interpreted as the module name""" + the identifier (up to the first /) is interpreted as the + module name""" if identifier: spec = self.find_spec_part(identifier) return spec_name_list(spec, identifier + "/") diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index 43b0005f77..f1023ce6ce 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -232,7 +232,7 @@ def _validate_type(spec, value, errors): else: return True -def _validate_item(spec, data, errors): +def _validate_item(spec, full, data, errors): if not _validate_type(spec, data, errors): return False elif type(data) == list: @@ -241,10 +241,10 @@ def _validate_item(spec, data, errors): if not _validate_type(list_spec, data_el, errors): return False if list_spec['item_type'] == "map": - if not _validate_item(list_spec, data_el, errors): + if not _validate_item(list_spec, full, data_el, errors): return False elif type(data) == dict: - if not _validate_spec_list(spec['map_item_spec'], data, errors): + if not _validate_spec_list(spec['map_item_spec'], full, data, errors): return False return True @@ -253,7 +253,7 @@ def _validate_spec(spec, full, data, errors): item_optional = spec['item_optional'] if item_name in data: - return _validate_item(spec, data[item_name], errors) + return _validate_item(spec, full, data[item_name], errors) elif full and not item_optional: if errors != None: errors.append("non-optional item " + item_name + " missing") diff --git a/src/lib/config/python/isc/config/datadefinition_test.py b/src/lib/config/python/isc/config/datadefinition_test.py index da2bc1b5d8..e266214189 100644 --- a/src/lib/config/python/isc/config/datadefinition_test.py +++ b/src/lib/config/python/isc/config/datadefinition_test.py @@ -76,7 +76,7 @@ class TestDataDefinition(unittest.TestCase): data_file = open(self.spec_file(datafile_name)) data_str = data_file.read() data = isc.cc.data.parse_value_str(data_str) - return dd.validate(data) + return dd.validate(True, data) def test_data_validation(self): self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data")) From 7ccf5c90c21553bb5514fb27ad9fa3ac96d2e3cf Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 09:50:03 +0000 Subject: [PATCH 22/81] type checking on client side implemented sample print_settings command git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@831 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10.py.in | 6 +- src/bin/bind10/bob.spec | 7 +- src/bin/bindctl/bindcmd.py | 17 +++-- src/lib/config/python/isc/config/ccsession.py | 4 ++ .../config/python/isc/config/config_data.py | 67 +++++++++---------- 5 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 23633c8b6f..02e6ca37d8 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -148,8 +148,10 @@ class BoB: print(command[1]["message"]) answer = isc.config.ccsession.create_answer(0) elif cmd == "print_settings": - print("Config:") - print(self.ccs.get_config()) + print("Full Config:") + full_config = self.ccs.get_full_config() + for item in full_config: + print(item + ": " + str(full_config[item])) answer = isc.config.ccsession.create_answer(0) else: answer = isc.config.ccsession.create_answer(1, "Unknown command") diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec index 71043ebd9a..1a3fbb86ad 100644 --- a/src/bin/bind10/bob.spec +++ b/src/bin/bind10/bob.spec @@ -29,12 +29,7 @@ { "command_name": "print_settings", "command_description": "Print some_string and some_int to stdout", - "command_args": [ { - "item_name": "message", - "item_type": "string", - "item_optional": True, - "item_default": "" - } ] + "command_args": [] }, { "command_name": "shutdown", diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index 99399c2c77..ca1180cad1 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -446,13 +446,16 @@ class BindCmdInterpreter(Cmd): elif cmd.command == "remove": self.config_data.remove_value(identifier, cmd.params['value']) elif cmd.command == "set": - parsed_value = None - try: - parsed_value = ast.literal_eval(cmd.params['value']) - except Exception as exc: - # ok could be an unquoted string, interpret as such - parsed_value = cmd.params['value'] - self.config_data.set_value(identifier, parsed_value) + if 'identifier' not in cmd.params: + print("Error: missing identifier or value") + else: + parsed_value = None + try: + parsed_value = ast.literal_eval(cmd.params['value']) + except Exception as exc: + # ok could be an unquoted string, interpret as such + parsed_value = cmd.params['value'] + self.config_data.set_value(identifier, parsed_value) elif cmd.command == "unset": self.config_data.unset(identifier) elif cmd.command == "revert": diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index bfaf22c7d2..2f248a28e1 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -117,6 +117,10 @@ class CCSession: """Returns the current or non-default configuration""" return self._config_data.get_local_config() + def get_full_config(self): + """Returns the current or non-default configuration""" + return self._config_data.get_full_config() + def get_config_data(self): """Returns the config_data part of the specification""" return self._config_data diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index d6aff78c09..76207e7174 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -25,36 +25,31 @@ import isc.config.datadefinition class ConfigDataError(Exception): pass -# -# hmm, these are more relevant for datadefition -# should we (re)move them? -# def check_type(specification, value): """Returns true if the value is of the correct type given the - specification""" + specification part relevant for the value""" if type(specification) == list: data_type = "list" else: data_type = specification['item_type'] if data_type == "integer" and type(value) != int: - raise DataTypeError(str(value) + " should be an integer") + raise isc.cc.data.DataTypeError(str(value) + " is not an integer") elif data_type == "real" and type(value) != double: - raise DataTypeError(str(value) + " should be a real") + raise isc.cc.data.DataTypeError(str(value) + " is not a real") elif data_type == "boolean" and type(value) != boolean: - raise DataTypeError(str(value) + " should be a boolean") + raise isc.cc.data.DataTypeError(str(value) + " is not a boolean") elif data_type == "string" and type(value) != str: - raise DataTypeError(str(value) + " should be a string") + raise isc.cc.data.DataTypeError(str(value) + " is not a string") elif data_type == "list": if type(value) != list: - raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__)) + raise isc.cc.data.DataTypeError(str(value) + " is not a list") else: - # todo: check subtypes etc for element in value: check_type(specification['list_item_spec'], element) elif data_type == "map" and type(value) != dict: - # todo: check subtypes etc - raise DataTypeError(str(value) + " should be a map") + # todo: check types of map contents too + raise isc.cc.data.DataTypeError(str(value) + " is not a map") def find_spec(element, identifier): """find the data definition for the given identifier @@ -119,20 +114,20 @@ class ConfigData: self.specification = specification self.data = {} - def get_item_list(self, identifier = None): + def get_item_list(self, identifier = None, recurse = False): if identifier: - spec = find_spec(self.specification, identifier) + spec = find_spec(self.specification.get_config_spec(), identifier, recurse) return spec_name_list(spec, identifier + "/") - return spec_name_list(self.specification) + return spec_name_list(self.specification.get_config_spec(), "", recurse) def get_value(self, identifier): """Returns a tuple where the first item is the value at the given identifier, and the second item is a bool which is true if the value is an unset default""" - value = find_no_exc(self.data, identifier) + value = isc.cc.data.find_no_exc(self.data, identifier) if value: return value, False - spec = find_spec(self.specification, identifier) + spec = find_spec(self.specification.get_config_data(), identifier) if spec and 'item_default' in spec: return spec['item_default'], True return None, False @@ -148,7 +143,15 @@ class ConfigData: def get_local_config(self): """Returns the non-default config values in a dict""" - return self.config(); + return self.data; + + def get_full_config(self): + items = self.get_item_list(None, True) + result = [] + for item in items: + value, default = self.get_value(item) + result.append(item + ": " + str(value)) + return result #def get_identifiers(self): # Returns a list containing all identifiers @@ -271,7 +274,6 @@ class MultiConfigData: module, sep, id = identifier.partition('/') spec = self.get_specification(module) if spec: - print("[XX] getpartspec") spec_part = find_spec(spec.get_config_data(), id) print(spec_part) if type(spec_part) == list: @@ -279,7 +281,6 @@ class MultiConfigData: entry = {} entry['name'] = item['item_name'] entry['type'] = item['item_type'] - print("[XX] getvalue") value, status = self.get_value("/" + identifier + "/" + item['item_name']) entry['value'] = value if status == self.LOCAL: @@ -295,8 +296,6 @@ class MultiConfigData: item = spec_part if item['item_type'] == 'list': li_spec = item['list_item_spec'] - print("[XX] item:") - print(item) l, status = self.get_value("/" + identifier) if l: for value in l: @@ -328,8 +327,9 @@ class MultiConfigData: def set_value(self, identifier, value): """Set the local value at the given identifier to value""" - # todo: validate - isc.cc.data.set(self._local_changes, identifier, value) + spec_part = self.find_spec_part(identifier) + if check_type(spec_part, value): + isc.cc.data.set(self._local_changes, identifier, value) def get_config_item_list(self, identifier = None): """Returns a list of strings containing the item_names of @@ -354,7 +354,6 @@ class UIConfigData(): self.request_specifications() self.request_current_config() a,b = self._data.get_value("/Boss/some_string") - print("[XX] a,b: " + str(a) + ", " + str(b)) def request_specifications(self): # this step should be unnecessary but is the current way cmdctl returns stuff @@ -403,9 +402,9 @@ class UIConfigData(): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) check_type(data_spec, [value]) - cur_list = find_no_exc(self.config_changes, identifier) + cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: - cur_list = find_no_exc(self.config.data, identifier) + cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) if not cur_list: cur_list = [] if value in cur_list: @@ -459,8 +458,6 @@ class OUIConfigData(): def get_data_specifications(self, conn): specs = {} allspecs = conn.send_GET('/config_spec') - print("[XX] allspecs:") - print(allspecs) def set(self, identifier, value): @@ -517,8 +514,6 @@ class OUIConfigData(): default: true if the value has been changed Throws DataNotFoundError if the identifier is bad """ - print("[XX] config:") - print(self.config) spec = find_spec(self.config, identifier) result = [] if type(spec) == dict: @@ -561,9 +556,9 @@ class OUIConfigData(): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) check_type(data_spec, [value]) - cur_list = find_no_exc(self.config_changes, identifier) + cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: - cur_list = find_no_exc(self.config.data, identifier) + cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) if not cur_list: cur_list = [] if value not in cur_list: @@ -576,9 +571,9 @@ class OUIConfigData(): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) check_type(data_spec, [value]) - cur_list = find_no_exc(self.config_changes, identifier) + cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: - cur_list = find_no_exc(self.config.data, identifier) + cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) if not cur_list: cur_list = [] if value in cur_list: From 976107dd7ee4cabff69a104d00130ee47a0b2c83 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 10:27:17 +0000 Subject: [PATCH 23/81] removed a few prints git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@832 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/cfgmgr.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index eab3883892..754d025b97 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -225,8 +225,6 @@ class ConfigManager: self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) # replace 'our' answer with that of the module answer, env = self.cc.group_recvmsg(False) - print("[XX] module responded with") - print(answer) rcode, val = isc.config.ccsession.parse_answer(answer) if rcode == 0: self.write_config() @@ -240,8 +238,6 @@ class ConfigManager: if module != "version": self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) answer, env = self.cc.group_recvmsg(False) - print("[XX] one module responded with") - print(answer) rcode, val = isc.config.ccsession.parse_answer(answer) if rcode != 0: got_error = True @@ -266,8 +262,6 @@ class ConfigManager: # todo: error checking (like keyerrors) answer = {} self.set_data_spec(spec) - print("[XX] cfgmgr add spec:") - print(spec) # We should make one general 'spec update for module' that # passes both specification and commands at once @@ -317,10 +311,6 @@ class ConfigManager: msg, env = self.cc.group_recvmsg(False) if msg: answer = self.handle_msg(msg); - print("[XX] CFGMGR Sending answer to UI:") - print(answer) - print("For command") - print(msg) self.cc.group_reply(env, answer) else: self.running = False From b8b0f55e1ad0c8dadc11c815c2673f7ef20a45bc Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 11:05:05 +0000 Subject: [PATCH 24/81] refactoring; renamed the DataDefinition class to the much more accurate ModuleSpec also renamed some ambiguously named functions therein git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@833 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth.spec | 2 +- src/bin/bind10/bind10.py.in | 2 +- src/bin/bind10/bob.spec | 2 +- src/bin/cmdctl/b10-cmdctl.py.in | 4 +- src/bin/parkinglot/parkinglot.spec | 2 +- src/lib/cc/cpp/parkinglot.spec | 2 +- src/lib/config/cpp/ccsession.cc | 2 +- src/lib/config/cpp/data_def.cc | 8 +- src/lib/config/cpp/data_def_unittests.cc | 8 +- src/lib/config/python/isc/config/ccsession.py | 11 +- src/lib/config/python/isc/config/cfgmgr.py | 70 ++++----- .../config/python/isc/config/cfgmgr_test.py | 30 ++-- .../config/python/isc/config/config_data.py | 73 +++++---- .../python/isc/config/config_data_test.py | 4 +- .../python/isc/config/datadefinition.py | 146 +++++++++--------- .../python/isc/config/datadefinition_test.py | 52 +++---- src/lib/config/testdata/spec1.spec | 2 +- src/lib/config/testdata/spec10.spec | 2 +- src/lib/config/testdata/spec11.spec | 2 +- src/lib/config/testdata/spec12.spec | 2 +- src/lib/config/testdata/spec13.spec | 2 +- src/lib/config/testdata/spec14.spec | 2 +- src/lib/config/testdata/spec15.spec | 2 +- src/lib/config/testdata/spec16.spec | 2 +- src/lib/config/testdata/spec17.spec | 2 +- src/lib/config/testdata/spec18.spec | 2 +- src/lib/config/testdata/spec19.spec | 2 +- src/lib/config/testdata/spec2.spec | 2 +- src/lib/config/testdata/spec20.spec | 2 +- src/lib/config/testdata/spec21.spec | 2 +- src/lib/config/testdata/spec22.spec | 2 +- src/lib/config/testdata/spec23.spec | 2 +- src/lib/config/testdata/spec3.spec | 2 +- src/lib/config/testdata/spec4.spec | 2 +- src/lib/config/testdata/spec5.spec | 2 +- src/lib/config/testdata/spec6.spec | 2 +- src/lib/config/testdata/spec7.spec | 2 +- src/lib/config/testdata/spec9.spec | 2 +- 38 files changed, 232 insertions(+), 230 deletions(-) diff --git a/src/bin/auth/auth.spec b/src/bin/auth/auth.spec index ad4b45e8e6..c1567e5c60 100644 --- a/src/bin/auth/auth.spec +++ b/src/bin/auth/auth.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Auth", "config_data": [ { "item_name": "default_name", diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 02e6ca37d8..95321d7b29 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -116,7 +116,7 @@ class BoB: print("[XX] handling new config:") print(new_config) errors = [] - if self.ccs.get_config_data().get_specification().validate(False, new_config, errors): + if self.ccs.get_config_spec().get_module_spec().validate(False, new_config, errors): print("[XX] new config validated") self.ccs.set_config(new_config) answer = isc.config.ccsession.create_answer(0) diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec index 1a3fbb86ad..b47bc9d4ab 100644 --- a/src/bin/bind10/bob.spec +++ b/src/bin/bind10/bob.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Boss", "config_data": [ { diff --git a/src/bin/cmdctl/b10-cmdctl.py.in b/src/bin/cmdctl/b10-cmdctl.py.in index 2cffa90d80..22a35a28ca 100644 --- a/src/bin/cmdctl/b10-cmdctl.py.in +++ b/src/bin/cmdctl/b10-cmdctl.py.in @@ -188,7 +188,7 @@ class CommandControl(): self.config_data = self.get_config_data() def get_cmd_specification(self): - return self.send_command('ConfigManager', 'get_commands') + return self.send_command('ConfigManager', 'get_commands_spec') def get_config_data(self): return self.send_command('ConfigManager', 'get_config') @@ -198,7 +198,7 @@ class CommandControl(): self.config_data = self.get_config_data() def get_data_specification(self): - return self.send_command('ConfigManager', 'get_data_spec') + return self.send_command('ConfigManager', 'get_module_spec') def handle_recv_msg(self): # Handle received message, if 'shutdown' is received, return False diff --git a/src/bin/parkinglot/parkinglot.spec b/src/bin/parkinglot/parkinglot.spec index 9ac75792e2..689c4657d4 100644 --- a/src/bin/parkinglot/parkinglot.spec +++ b/src/bin/parkinglot/parkinglot.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "ParkingLot", "config_data": [ { diff --git a/src/lib/cc/cpp/parkinglot.spec b/src/lib/cc/cpp/parkinglot.spec index 4b6e98abee..90eb2686ac 100644 --- a/src/lib/cc/cpp/parkinglot.spec +++ b/src/lib/cc/cpp/parkinglot.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "ParkingLot", "config_data": [ { diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 574faa82d0..8c88c2b6e3 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -81,7 +81,7 @@ CommandSession::CommandSession(std::string spec_file_name, read_data_definition(spec_file_name); sleep(1); - module_name_ = data_definition_.getDefinition()->get("data_specification")->get("module_name")->stringValue(); + module_name_ = data_definition_.getDefinition()->get("module_spec")->get("module_name")->stringValue(); config_handler_ = config_handler; command_handler_ = command_handler; diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/data_def.cc index d029c4aa9c..2b716dccc6 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/data_def.cc @@ -141,10 +141,10 @@ check_data_specification(const ElementPtr& spec) { static void check_definition(const ElementPtr& def) { - if (!def->contains("data_specification")) { - throw DataDefinitionError("Data specification does not contain data_specification element"); + if (!def->contains("module_spec")) { + throw DataDefinitionError("Data specification does not contain module_spec element"); } else { - check_data_specification(def->get("data_specification")); + check_data_specification(def->get("module_spec")); } } @@ -276,7 +276,7 @@ DataDefinition::validate_spec_list(const ElementPtr spec, const ElementPtr data) // form, we should do that in the constructor bool DataDefinition::validate(const ElementPtr data) { - ElementPtr spec = definition->find("data_specification/config_data"); + ElementPtr spec = definition->find("module_spec/config_data"); return validate_spec_list(spec, data); } diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/data_def_unittests.cc index 45fe0e2651..707f59adbe 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/data_def_unittests.cc @@ -47,11 +47,11 @@ TEST(DataDefinition, ReadingSpecfiles) { // Tests whether we can open specfiles and if we get the // right parse errors DataDefinition dd = DataDefinition(specfile("spec1.spec")); - EXPECT_EQ(dd.getDefinition()->get("data_specification") + EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); dd = DataDefinition(specfile("spec2.spec")); - EXPECT_EQ(dd.getDefinition()->get("data_specification") + EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("config_data")->size(), 6); data_def_error("doesnotexist", "Error opening ", @@ -61,7 +61,7 @@ TEST(DataDefinition, ReadingSpecfiles) { std::ifstream file; file.open(specfile("spec1.spec").c_str()); dd = DataDefinition(file); - EXPECT_EQ(dd.getDefinition()->get("data_specification") + EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); } @@ -96,7 +96,7 @@ TEST(DataDefinition, SpecfileConfigData) data_def_error("spec7.spec", "module_name missing in {}"); data_def_error("spec8.spec", - "Data specification does not contain data_specification element"); + "Data specification does not contain module_spec element"); data_def_error("spec16.spec", "config_data is not a list of elements"); data_def_error("spec21.spec", diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 2f248a28e1..a39cc83ec4 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -80,7 +80,7 @@ class CCSession: config_handler and command_handler are callback functions, see set_config_handler and set_command_handler for more information on their signatures.""" - data_definition = isc.config.data_spec_from_file(spec_file_name) + data_definition = isc.config.module_spec_from_file(spec_file_name) self._config_data = isc.config.config_data.ConfigData(data_definition) self._module_name = data_definition.get_module_name() @@ -121,9 +121,8 @@ class CCSession: """Returns the current or non-default configuration""" return self._config_data.get_full_config() - def get_config_data(self): - """Returns the config_data part of the specification""" - return self._config_data + def get_module_spec(self): + return self._config_data.get_module_spec() def close(self): """Close the session to the command channel""" @@ -165,7 +164,7 @@ class CCSession: def __send_spec(self): """Sends the data specification to the configuration manager""" print("[XX] send spec for " + self._module_name + " to ConfigManager") - self._session.group_sendmsg({ "data_specification": self._config_data.get_specification().get_definition() }, "ConfigManager") + self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager") answer, env = self._session.group_recvmsg(False) print("[XX] got answer from cfgmgr:") print(answer) @@ -176,7 +175,7 @@ class CCSession: answer, env = self._session.group_recvmsg(False) rcode, value = parse_answer(answer) if rcode == 0: - if self._config_data.get_specification().validate(False, value): + if self._config_data.get_module_spec().validate(False, value): self._config_data.set_local_config(value); if self._config_handler: self._config_handler(value) diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 754d025b97..cc5a904946 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -106,12 +106,12 @@ 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.data_specs + # remove these and use self.module_specs #self.commands = {} self.data_definitions = {} self.data_path = data_path - self.data_specs = {} + self.module_specs = {} self.config = ConfigManagerData(data_path) if session: self.cc = session @@ -125,38 +125,38 @@ class ConfigManager: """Notifies the Boss module that the Config Manager is running""" self.cc.group_sendmsg({"running": "configmanager"}, "Boss") - def set_data_spec(self, spec): - #data_def = isc.config.DataDefinition(spec) - self.data_specs[spec.get_module_name()] = spec + def set_module_spec(self, spec): + #data_def = isc.config.ModuleSpec(spec) + self.module_specs[spec.get_module_name()] = spec - def get_data_spec(self, module_name): - if module_name in self.data_specs: - return self.data_specs[module_name] + def get_module_spec(self, module_name): + if module_name in self.module_specs: + return self.module_specs[module_name] - def get_config_data(self, name = None): + def get_config_spec(self, name = None): """Returns a dict containing 'module_name': config_data for all modules. If name is specified, only that module will be included""" config_data = {} if name: - if name in self.data_specs: - config_data[name] = self.data_specs[name].get_data + if name in self.module_specs: + config_data[name] = self.module_specs[name].get_data else: - for module_name in self.data_specs.keys(): - config_data[module_name] = self.data_specs[module_name].get_config_data() + for module_name in self.module_specs.keys(): + config_data[module_name] = self.module_specs[module_name].get_config_spec() return config_data - def get_commands(self, name = None): + def get_commands_spec(self, name = None): """Returns a dict containing 'module_name': commands_dict for all modules. If name is specified, only that module will be included""" commands = {} if name: - if name in self.data_specs: - commands[name] = self.data_specs[name].get_commands + if name in self.module_specs: + commands[name] = self.module_specs[name].get_commands_spec else: - for module_name in self.data_specs.keys(): - commands[module_name] = self.data_specs[module_name].get_commands() + for module_name in self.module_specs.keys(): + commands[module_name] = self.module_specs[module_name].get_commands_spec() return commands def read_config(self): @@ -173,19 +173,19 @@ class ConfigManager: at the path specificied at init()""" self.config.write_to_file() - def _handle_get_data_spec(self, cmd): + def _handle_get_module_spec(self, cmd): answer = {} if len(cmd) > 1: if type(cmd[1]) == dict: if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': module_name = cmd[1]['module_name'] - answer = isc.config.ccsession.create_answer(0, self.get_config_data(module_name)) + answer = isc.config.ccsession.create_answer(0, self.get_config_spec(module_name)) else: - answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_data_spec command") + answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_module_spec command") else: - answer = isc.config.ccsession.create_answer(1, "Bad get_data_spec command, argument not a dict") + answer = isc.config.ccsession.create_answer(1, "Bad get_module_spec command, argument not a dict") else: - answer = isc.config.ccsession.create_answer(0, self.get_config_data()) + answer = isc.config.ccsession.create_answer(0, self.get_config_spec()) return answer def _handle_get_config(self, cmd): @@ -256,17 +256,17 @@ class ConfigManager: return answer - def _handle_data_specification(self, spec): + def _handle_module_spec(self, spec): # todo: validate? (no direct access to spec as - # todo: use DataDefinition class + # todo: use ModuleSpec class # todo: error checking (like keyerrors) answer = {} - self.set_data_spec(spec) + self.set_module_spec(spec) # We should make one general 'spec update for module' that # passes both specification and commands at once - self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_data() ] }, "Cmd-Ctrld") - self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands() ] }, "Cmd-Ctrld") + self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_spec() ] }, "Cmd-Ctrld") + self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands_spec() ] }, "Cmd-Ctrld") answer = isc.config.ccsession.create_answer(0) return answer @@ -276,10 +276,10 @@ class ConfigManager: if "command" in msg: cmd = msg["command"] try: - if cmd[0] == "get_commands": - answer = isc.config.ccsession.create_answer(0, self.get_commands()) - elif cmd[0] == "get_data_spec": - answer = self._handle_get_data_spec(cmd) + if cmd[0] == "get_commands_spec": + answer = isc.config.ccsession.create_answer(0, self.get_commands_spec()) + elif cmd[0] == "get_module_spec": + answer = self._handle_get_module_spec(cmd) elif cmd[0] == "get_config": answer = self._handle_get_config(cmd) elif cmd[0] == "set_config": @@ -293,10 +293,10 @@ class ConfigManager: except IndexError as ie: answer = isc.config.ccsession.create_answer(1, "Missing argument in command: " + str(ie)) raise ie - elif "data_specification" in msg: + elif "module_spec" in msg: try: - answer = self._handle_data_specification(isc.config.DataDefinition(msg["data_specification"])) - except isc.config.DataDefinitionError as dde: + answer = self._handle_module_spec(isc.config.ModuleSpec(msg["module_spec"])) + except isc.config.ModuleSpecError as dde: answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde)) elif 'result' in msg: # this seems wrong, might start pingpong diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 909c9017ac..af7e6f18e7 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -109,10 +109,10 @@ class TestConfigManager(unittest.TestCase): self.fake_session = FakeCCSession() self.cm = ConfigManager(self.data_path, self.fake_session) self.name = "TestModule" - self.spec = isc.config.data_spec_from_file(self.data_path + os.sep + "/spec2.spec") + self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec") def test_init(self): - self.assert_(self.cm.data_specs == {}) + self.assert_(self.cm.module_specs == {}) self.assert_(self.cm.data_path == self.data_path) self.assert_(self.cm.config != None) self.assert_(self.fake_session.has_subscription("ConfigManager")) @@ -134,14 +134,14 @@ class TestConfigManager(unittest.TestCase): self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']}) self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']}) self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: ['badcommand']"]}) - self._handle_msg_helper({ "command": [ "get_commands" ] }, { 'result': [ 0, {} ]}) - self._handle_msg_helper({ "command": [ "get_data_spec" ] }, { 'result': [ 0, {} ]}) - #self._handle_msg_helper({ "command": [ "get_data_spec", { "module_name": "nosuchmodule" } ] }, + self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]}) + self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]}) + #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] }, # {'result': [1, 'No specification for module nosuchmodule']}) - self._handle_msg_helper({ "command": [ "get_data_spec", 1 ] }, - {'result': [1, 'Bad get_data_spec command, argument not a dict']}) - self._handle_msg_helper({ "command": [ "get_data_spec", { } ] }, - {'result': [1, 'Bad module_name in get_data_spec command']}) + self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] }, + {'result': [1, 'Bad get_module_spec command, argument not a dict']}) + self._handle_msg_helper({ "command": [ "get_module_spec", { } ] }, + {'result': [1, 'Bad module_name in get_module_spec command']}) self._handle_msg_helper({ "command": [ "get_config" ] }, { 'result': [ 0, { 'version': 1} ]}) self._handle_msg_helper({ "command": [ "get_config", { "module_name": "nosuchmodule" } ] }, {'result': [0, {}]}) @@ -174,16 +174,16 @@ class TestConfigManager(unittest.TestCase): self.assertEqual({'config_update': {'test': 124}}, self.fake_session.get_message(self.name, None)) self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) - self._handle_msg_helper({ "data_specification": - self.spec.get_definition() + self._handle_msg_helper({ "module_spec": + self.spec.get_full_spec() }, {'result': [0]}) - self._handle_msg_helper({ "data_specification": + self._handle_msg_helper({ "module_spec": { 'foo': 1 } }, - {'result': [1, 'Error in data definition: no module_name in data_specification']}) - self._handle_msg_helper({ "command": [ "get_data_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_config_spec() } ]}) - self._handle_msg_helper({ "command": [ "get_commands" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands() } ]}) + {'result': [1, 'Error in data definition: no module_name in module_spec']}) + self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_config_spec() } ]}) + self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]}) # re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages) #self.assertEqual(len(self.fake_session.message_queue), 2) # the name here is actually wrong (and hardcoded), but needed in the current version diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 76207e7174..bcc68e32ca 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -108,9 +108,9 @@ class ConfigData: def __init__(self, specification): """Initialize a ConfigData instance. If specification is not - of type DataDefinition, a ConfigDataError is raised.""" - if type(specification) != isc.config.DataDefinition: - raise ConfigDataError("specification is of type " + str(type(specification)) + ", not DataDefinition") + of type ModuleSpec, a ConfigDataError is raised.""" + if type(specification) != isc.config.ModuleSpec: + raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec") self.specification = specification self.data = {} @@ -127,14 +127,13 @@ class ConfigData: value = isc.cc.data.find_no_exc(self.data, identifier) if value: return value, False - spec = find_spec(self.specification.get_config_data(), identifier) + spec = find_spec(self.specification.get_config_spec(), identifier) if spec and 'item_default' in spec: return spec['item_default'], True return None, False - def get_specification(self): - """Returns the datadefinition""" - print(self.specification) + def get_module_spec(self): + """Returns the ModuleSpec object associated with this ConfigData""" return self.specification def set_local_config(self, data): @@ -147,10 +146,10 @@ class ConfigData: def get_full_config(self): items = self.get_item_list(None, True) - result = [] + result = {} for item in items: value, default = self.get_value(item) - result.append(item + ": " + str(value)) + result[item] = value return result #def get_identifiers(self): @@ -174,11 +173,11 @@ class MultiConfigData: self._local_changes = {} def set_specification(self, spec): - if type(spec) != isc.config.DataDefinition: + if type(spec) != isc.config.ModuleSpec: raise Exception("not a datadef") self._specifications[spec.get_module_name()] = spec - def get_specification(self, module): + def get_module_spec(self, module): if module in self._specifications: return self._specifications[module] else: @@ -191,7 +190,7 @@ class MultiConfigData: identifier = identifier[1:] module, sep, id = identifier.partition("/") try: - return find_spec(self._specifications[module].get_config_data(), id) + return find_spec(self._specifications[module].get_config_spec(), id) except isc.cc.data.DataNotFoundError as dnfe: return None @@ -223,7 +222,7 @@ class MultiConfigData: identifier = identifier[1:] module, sep, id = identifier.partition("/") try: - spec = find_spec(self._specifications[module].get_config_data(), id) + spec = find_spec(self._specifications[module].get_config_spec(), id) if 'item_default' in spec: return spec['item_default'] else: @@ -272,9 +271,9 @@ class MultiConfigData: if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition('/') - spec = self.get_specification(module) + spec = self.get_module_spec(module) if spec: - spec_part = find_spec(spec.get_config_data(), id) + spec_part = find_spec(spec.get_config_spec(), id) print(spec_part) if type(spec_part) == list: for item in spec_part: @@ -370,7 +369,7 @@ class UIConfigData(): if module in commands and commands[module]: cur_spec['commands'] = commands[module] - self._data.set_specification(isc.config.DataDefinition(cur_spec)) + self._data.set_specification(isc.config.ModuleSpec(cur_spec)) def request_current_config(self): config = self._conn.send_GET('/config_data') @@ -385,8 +384,8 @@ class UIConfigData(): return self._data.set_value(identifier, value); def add_value(self, identifier, value_str): - data_spec = self._data.find_spec_part(identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): + module_spec = self._data.find_spec_part(identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") value = isc.cc.data.parse_value_str(value_str) cur_list, status = self.get_value(identifier) @@ -397,11 +396,11 @@ class UIConfigData(): self.set_value(identifier, cur_list) def remove_value(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): + module_spec = find_spec(self.config.specification, identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) - check_type(data_spec, [value]) + check_type(module_spec, [value]) cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) @@ -434,28 +433,28 @@ class OUIConfigData(): def __init__(self, conn): # the specs dict contains module: configdata elements # these should all be replaced by the new stuff - data_spec = self.get_data_specification(conn) - self.config = data_spec - self.get_config_data(conn) + module_spec = self.get_module_spec(conn) + self.config = module_spec + self.get_config_spec(conn) self.config_changes = {} # self.config_ - self.specs = self.get_data_specifications(conn) + self.specs = self.get_module_specs(conn) - def get_config_data(self, conn): + def get_config_spec(self, conn): data = conn.send_GET('/config_data') def send_changes(self, conn): conn.send_POST('/ConfigManager/set_config', self.config_changes) # Get latest config data - self.get_config_data(conn) + self.get_config_spec(conn) self.config_changes = {} - def get_data_specification(self, conn): + def get_module_spec(self, conn): return conn.send_GET('/config_spec') - def get_data_specifications(self, conn): + def get_module_specs(self, conn): specs = {} allspecs = conn.send_GET('/config_spec') @@ -551,11 +550,11 @@ class OUIConfigData(): return result def add(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): + module_spec = find_spec(self.config.specification, identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) - check_type(data_spec, [value]) + check_type(module_spec, [value]) cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) @@ -566,11 +565,11 @@ class OUIConfigData(): set(self.config_changes, identifier, cur_list) def remove(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) - if (type(data_spec) != dict or "list_item_spec" not in data_spec): + module_spec = find_spec(self.config.specification, identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") value = parse_value_str(value_str) - check_type(data_spec, [value]) + check_type(module_spec, [value]) cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) if not cur_list: cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) @@ -581,9 +580,9 @@ class OUIConfigData(): set(self.config_changes, identifier, cur_list) def set(self, identifier, value_str): - data_spec = find_spec(self.config.specification, identifier) + module_spec = find_spec(self.config.specification, identifier) value = parse_value_str(value_str) - check_type(data_spec, value) + check_type(module_spec, value) set(self.config_changes, identifier, value) def unset(self, identifier): diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 5c22dcf821..fe27a7c10e 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -29,8 +29,8 @@ class TestConfigData(unittest.TestCase): else: self.data_path = "../../../testdata" - def test_data_spec_from_file(self): - spec = isc.config.data_spec_from_file(self.data_path + os.sep + "spec1.spec") + def test_module_spec_from_file(self): + spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") cd = ConfigData(spec) self.assertEqual(cd.specification, spec) self.assertEqual(cd.data, {}) diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index f1023ce6ce..3d4e1e75c0 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -24,31 +24,31 @@ import isc.cc.data # file objects are passed around as _io.TextIOWrapper objects # import that so we can check those types -class DataDefinitionError(Exception): +class ModuleSpecError(Exception): pass -def data_spec_from_file(spec_file, check = True): - data_spec = None +def module_spec_from_file(spec_file, check = True): + module_spec = None if hasattr(spec_file, 'read'): - data_spec = ast.literal_eval(spec_file.read(-1)) + module_spec = ast.literal_eval(spec_file.read(-1)) elif type(spec_file) == str: file = open(spec_file) - data_spec = ast.literal_eval(file.read(-1)) + module_spec = ast.literal_eval(file.read(-1)) file.close() else: - raise DataDefinitionError("spec_file not a str or file-like object") - if 'data_specification' not in data_spec: - raise DataDefinitionError("Data definition has no data_specification element") + raise ModuleSpecError("spec_file not a str or file-like object") + if 'module_spec' not in module_spec: + raise ModuleSpecError("Data definition has no module_spec element") - return DataDefinition(data_spec['data_specification'], check) + return ModuleSpec(module_spec['module_spec'], check) -class DataDefinition: - def __init__(self, data_spec, check = True): - if type(data_spec) != dict: - raise DataDefinitionError("data_spec is of type " + str(type(data_spec)) + ", not dict") +class ModuleSpec: + def __init__(self, module_spec, check = True): + if type(module_spec) != dict: + raise ModuleSpecError("module_spec is of type " + str(type(module_spec)) + ", not dict") if check: - _check(data_spec) - self._data_spec = data_spec + _check(module_spec) + self._module_spec = module_spec def validate(self, full, data, errors = None): """Check whether the given piece of data conforms to this @@ -60,7 +60,11 @@ class DataDefinition: non-optional missing values. Set this to False if you want to validate only a part of a configuration tree (like a list of non-default values)""" - data_def = self.get_definition() + print("[XX] validate called with data:") + print(data) + data_def = self.get_config_spec() + print("[XX] data def:") + print(data_def) if 'config_data' not in data_def: if errors: errors.append("The is no config_data for this specification") @@ -69,111 +73,111 @@ class DataDefinition: def get_module_name(self): - return self._data_spec['module_name'] + return self._module_spec['module_name'] - def get_definition(self): - return self._data_spec + def get_full_spec(self): + """Returns a dict representation of the full module specification""" + return self._module_spec def get_config_spec(self): - if 'config_data' in self._data_spec: - return self._data_spec['config_data'] + """Returns a dict representation of the configuration data part + of the specification, or None if there is none.""" + if 'config_data' in self._module_spec: + return self._module_spec['config_data'] else: return None - def get_commands(self): - if 'commands' in self._data_spec: - return self._data_spec['commands'] + def get_commands_spec(self): + """Returns a dict representation of the commands part of the + specification, or None if there is none.""" + if 'commands' in self._module_spec: + return self._module_spec['commands'] else: return None - def get_config_data(self): - if 'config_data' in self._data_spec: - return self._data_spec['config_data'] - else: - return None - def __str__(self): - return self._data_spec.__str__() + """Returns a string representation of the full specification""" + return self._module_spec.__str__() -def _check(data_spec): +def _check(module_spec): """Checks the full specification. This is a dict that contains the - element "data_specification", which is in itself a dict that + element "module_spec", which is in itself a dict that must contain at least a "module_name" (string) and optionally a "config_data" and a "commands" element, both of which are lists - of dicts. Raises a DataDefinitionError if there is a problem.""" - if type(data_spec) != dict: - raise DataDefinitionError("data specification not a dict") - if "module_name" not in data_spec: - raise DataDefinitionError("no module_name in data_specification") - if "config_data" in data_spec: - _check_config_spec(data_spec["config_data"]) - if "commands" in data_spec: - _check_command_spec(data_spec["commands"]) + of dicts. Raises a ModuleSpecError if there is a problem.""" + if type(module_spec) != dict: + raise ModuleSpecError("data specification not a dict") + if "module_name" not in module_spec: + raise ModuleSpecError("no module_name in module_spec") + if "config_data" in module_spec: + _check_config_spec(module_spec["config_data"]) + if "commands" in module_spec: + _check_command_spec(module_spec["commands"]) def _check_config_spec(config_data): # config data is a list of items represented by dicts that contain # things like "item_name", depending on the type they can have # specific subitems """Checks a list that contains the configuration part of the - specification. Raises a DataDefinitionError if there is a + specification. Raises a ModuleSpecError if there is a problem.""" if type(config_data) != list: - raise DataDefinitionError("config_data is of type " + str(type(config_data)) + ", not a list of items") + raise ModuleSpecError("config_data is of type " + str(type(config_data)) + ", not a list of items") for config_item in config_data: _check_item_spec(config_item) def _check_command_spec(commands): """Checks the list that contains a set of commands. Raises a - DataDefinitionError is there is an error""" + ModuleSpecError is there is an error""" if type(commands) != list: - raise DataDefinitionError("commands is not a list of commands") + raise ModuleSpecError("commands is not a list of commands") for command in commands: if type(command) != dict: - raise DataDefinitionError("command in commands list is not a dict") + raise ModuleSpecError("command in commands list is not a dict") if "command_name" not in command: - raise DataDefinitionError("no command_name in command item") + raise ModuleSpecError("no command_name in command item") command_name = command["command_name"] if type(command_name) != str: - raise DataDefinitionError("command_name not a string: " + str(type(command_name))) + raise ModuleSpecError("command_name not a string: " + str(type(command_name))) if "command_description" in command: if type(command["command_description"]) != str: - raise DataDefinitionError("command_description not a string in " + command_name) + raise ModuleSpecError("command_description not a string in " + command_name) if "command_args" in command: if type(command["command_args"]) != list: - raise DataDefinitionError("command_args is not a list in " + command_name) + raise ModuleSpecError("command_args is not a list in " + command_name) for command_arg in command["command_args"]: if type(command_arg) != dict: - raise DataDefinitionError("command argument not a dict in " + command_name) + raise ModuleSpecError("command argument not a dict in " + command_name) _check_item_spec(command_arg) else: - raise DataDefinitionError("command_args missing in " + command_name) + raise ModuleSpecError("command_args missing in " + command_name) pass def _check_item_spec(config_item): """Checks the dict that defines one config item (i.e. containing "item_name", "item_type", etc. - Raises a DataDefinitionError if there is an error""" + Raises a ModuleSpecError if there is an error""" if type(config_item) != dict: - raise DataDefinitionError("item spec not a dict") + raise ModuleSpecError("item spec not a dict") if "item_name" not in config_item: - raise DataDefinitionError("no item_name in config item") + raise ModuleSpecError("no item_name in config item") if type(config_item["item_name"]) != str: - raise DataDefinitionError("item_name is not a string: " + str(config_item["item_name"])) + raise ModuleSpecError("item_name is not a string: " + str(config_item["item_name"])) item_name = config_item["item_name"] if "item_type" not in config_item: - raise DataDefinitionError("no item_type in config item") + raise ModuleSpecError("no item_type in config item") item_type = config_item["item_type"] if type(item_type) != str: - raise DataDefinitionError("item_type in " + item_name + " is not a string: " + str(type(item_type))) + raise ModuleSpecError("item_type in " + item_name + " is not a string: " + str(type(item_type))) if item_type not in ["integer", "real", "boolean", "string", "list", "map", "any"]: - raise DataDefinitionError("unknown item_type in " + item_name + ": " + item_type) + raise ModuleSpecError("unknown item_type in " + item_name + ": " + item_type) if "item_optional" in config_item: if type(config_item["item_optional"]) != bool: - raise DataDefinitionError("item_default in " + item_name + " is not a boolean") + raise ModuleSpecError("item_default in " + item_name + " is not a boolean") if not config_item["item_optional"] and "item_default" not in config_item: - raise DataDefinitionError("no default value for non-optional item " + item_name) + raise ModuleSpecError("no default value for non-optional item " + item_name) else: - raise DataDefinitionError("item_optional not in item " + item_name) + raise ModuleSpecError("item_optional not in item " + item_name) if "item_default" in config_item: item_default = config_item["item_default"] if (item_type == "integer" and type(item_default) != int) or \ @@ -182,22 +186,22 @@ def _check_item_spec(config_item): (item_type == "string" and type(item_default) != str) or \ (item_type == "list" and type(item_default) != list) or \ (item_type == "map" and type(item_default) != dict): - raise DataDefinitionError("Wrong type for item_default in " + item_name) + raise ModuleSpecError("Wrong type for item_default in " + item_name) # TODO: once we have check_type, run the item default through that with the list|map_item_spec if item_type == "list": if "list_item_spec" not in config_item: - raise DataDefinitionError("no list_item_spec in list item " + item_name) + raise ModuleSpecError("no list_item_spec in list item " + item_name) if type(config_item["list_item_spec"]) != dict: - raise DataDefinitionError("list_item_spec in " + item_name + " is not a dict") + raise ModuleSpecError("list_item_spec in " + item_name + " is not a dict") _check_item_spec(config_item["list_item_spec"]) if item_type == "map": if "map_item_spec" not in config_item: - raise DataDefinitionError("no map_item_sepc in map item " + item_name) + raise ModuleSpecError("no map_item_sepc in map item " + item_name) if type(config_item["map_item_spec"]) != list: - raise DataDefinitionError("map_item_spec in " + item_name + " is not a list") + raise ModuleSpecError("map_item_spec in " + item_name + " is not a list") for map_item in config_item["map_item_spec"]: if type(map_item) != dict: - raise DataDefinitionError("map_item_spec element is not a dict") + raise ModuleSpecError("map_item_spec element is not a dict") _check_item_spec(map_item) @@ -261,8 +265,8 @@ def _validate_spec(spec, full, data, errors): else: return True -def _validate_spec_list(data_spec, full, data, errors): - for spec_item in data_spec: +def _validate_spec_list(module_spec, full, data, errors): + for spec_item in module_spec: if not _validate_spec(spec_item, full, data, errors): return False return True diff --git a/src/lib/config/python/isc/config/datadefinition_test.py b/src/lib/config/python/isc/config/datadefinition_test.py index e266214189..28eed4fc58 100644 --- a/src/lib/config/python/isc/config/datadefinition_test.py +++ b/src/lib/config/python/isc/config/datadefinition_test.py @@ -19,10 +19,10 @@ import unittest import os -from isc.config import DataDefinition, DataDefinitionError +from isc.config import ModuleSpec, ModuleSpecError import isc.cc.data -class TestDataDefinition(unittest.TestCase): +class TestModuleSpec(unittest.TestCase): def setUp(self): if 'CONFIG_TESTDATA_PATH' in os.environ: @@ -34,12 +34,12 @@ class TestDataDefinition(unittest.TestCase): return(self.data_path + os.sep + filename) def read_spec_file(self, filename): - return isc.config.data_spec_from_file(self.spec_file(filename)) + return isc.config.module_spec_from_file(self.spec_file(filename)) def spec1(self, dd): - data_spec = dd.get_definition() - self.assert_('module_name' in data_spec) - self.assertEqual(data_spec['module_name'], "Spec1") + module_spec = dd.get_full_spec() + self.assert_('module_name' in module_spec) + self.assertEqual(module_spec['module_name'], "Spec1") def test_open_file_name(self): dd = self.read_spec_file("spec1.spec") @@ -47,29 +47,29 @@ class TestDataDefinition(unittest.TestCase): def test_open_file_obj(self): file1 = open(self.spec_file("spec1.spec")) - dd = isc.config.data_spec_from_file(file1) + dd = isc.config.module_spec_from_file(file1) self.spec1(dd) def test_bad_specfiles(self): - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec3.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec4.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec5.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec6.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec7.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec8.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec9.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec10.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec11.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec12.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec13.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec14.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec15.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec16.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec17.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec18.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec19.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec20.spec") - self.assertRaises(DataDefinitionError, self.read_spec_file, "spec21.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec3.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec4.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec5.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec6.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec7.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec8.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec9.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec10.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec11.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec12.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec13.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec14.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec15.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec16.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec17.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec18.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec19.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec20.spec") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec21.spec") def validate_data(self, specfile_name, datafile_name): dd = self.read_spec_file(specfile_name); diff --git a/src/lib/config/testdata/spec1.spec b/src/lib/config/testdata/spec1.spec index 702e09b6d0..05a3794cdf 100644 --- a/src/lib/config/testdata/spec1.spec +++ b/src/lib/config/testdata/spec1.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec1" } } diff --git a/src/lib/config/testdata/spec10.spec b/src/lib/config/testdata/spec10.spec index bd46c7f2ef..052c25d57f 100644 --- a/src/lib/config/testdata/spec10.spec +++ b/src/lib/config/testdata/spec10.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec11.spec b/src/lib/config/testdata/spec11.spec index edf26e0516..b8c4eac555 100644 --- a/src/lib/config/testdata/spec11.spec +++ b/src/lib/config/testdata/spec11.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec12.spec b/src/lib/config/testdata/spec12.spec index 3fa4906d50..b0f5972e88 100644 --- a/src/lib/config/testdata/spec12.spec +++ b/src/lib/config/testdata/spec12.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec13.spec b/src/lib/config/testdata/spec13.spec index 50f504de4c..b054cbcbaa 100644 --- a/src/lib/config/testdata/spec13.spec +++ b/src/lib/config/testdata/spec13.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec14.spec b/src/lib/config/testdata/spec14.spec index 28b76d7ad9..209f7f2f14 100644 --- a/src/lib/config/testdata/spec14.spec +++ b/src/lib/config/testdata/spec14.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec15.spec b/src/lib/config/testdata/spec15.spec index 8bac51e786..d4c4dd6b6f 100644 --- a/src/lib/config/testdata/spec15.spec +++ b/src/lib/config/testdata/spec15.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec16.spec b/src/lib/config/testdata/spec16.spec index 367ab5c48c..e78bc2edc0 100644 --- a/src/lib/config/testdata/spec16.spec +++ b/src/lib/config/testdata/spec16.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": 1 } diff --git a/src/lib/config/testdata/spec17.spec b/src/lib/config/testdata/spec17.spec index 092ec0d9ef..e99f432c2e 100644 --- a/src/lib/config/testdata/spec17.spec +++ b/src/lib/config/testdata/spec17.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": [ { diff --git a/src/lib/config/testdata/spec18.spec b/src/lib/config/testdata/spec18.spec index a17e2492b2..e3854aa927 100644 --- a/src/lib/config/testdata/spec18.spec +++ b/src/lib/config/testdata/spec18.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": [ { diff --git a/src/lib/config/testdata/spec19.spec b/src/lib/config/testdata/spec19.spec index 4c1ad8ce3d..1b3c703656 100644 --- a/src/lib/config/testdata/spec19.spec +++ b/src/lib/config/testdata/spec19.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": [ { diff --git a/src/lib/config/testdata/spec2.spec b/src/lib/config/testdata/spec2.spec index 73388637db..7745a55a97 100644 --- a/src/lib/config/testdata/spec2.spec +++ b/src/lib/config/testdata/spec2.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec20.spec b/src/lib/config/testdata/spec20.spec index 827679a600..7861472bf5 100644 --- a/src/lib/config/testdata/spec20.spec +++ b/src/lib/config/testdata/spec20.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": [ { diff --git a/src/lib/config/testdata/spec21.spec b/src/lib/config/testdata/spec21.spec index 3c5e2f687a..0af430252d 100644 --- a/src/lib/config/testdata/spec21.spec +++ b/src/lib/config/testdata/spec21.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": 1 } diff --git a/src/lib/config/testdata/spec22.spec b/src/lib/config/testdata/spec22.spec index 8da8ced6fd..08c2798cb5 100644 --- a/src/lib/config/testdata/spec22.spec +++ b/src/lib/config/testdata/spec22.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "value1", diff --git a/src/lib/config/testdata/spec23.spec b/src/lib/config/testdata/spec23.spec index a5e10edb73..9c4cc21eaa 100644 --- a/src/lib/config/testdata/spec23.spec +++ b/src/lib/config/testdata/spec23.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "commands": [ { diff --git a/src/lib/config/testdata/spec3.spec b/src/lib/config/testdata/spec3.spec index f6a6ba08cf..69b7f2bc4c 100644 --- a/src/lib/config/testdata/spec3.spec +++ b/src/lib/config/testdata/spec3.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec3", "config_data": [ { diff --git a/src/lib/config/testdata/spec4.spec b/src/lib/config/testdata/spec4.spec index 510be468a0..1d24c61019 100644 --- a/src/lib/config/testdata/spec4.spec +++ b/src/lib/config/testdata/spec4.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec5.spec b/src/lib/config/testdata/spec5.spec index be8724954c..515424a9e2 100644 --- a/src/lib/config/testdata/spec5.spec +++ b/src/lib/config/testdata/spec5.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec6.spec b/src/lib/config/testdata/spec6.spec index 0c544fa651..d0fc5f0110 100644 --- a/src/lib/config/testdata/spec6.spec +++ b/src/lib/config/testdata/spec6.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", diff --git a/src/lib/config/testdata/spec7.spec b/src/lib/config/testdata/spec7.spec index 6555c1513e..42f8b7aece 100644 --- a/src/lib/config/testdata/spec7.spec +++ b/src/lib/config/testdata/spec7.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { } } diff --git a/src/lib/config/testdata/spec9.spec b/src/lib/config/testdata/spec9.spec index 2a90066d47..34a6a39d76 100644 --- a/src/lib/config/testdata/spec9.spec +++ b/src/lib/config/testdata/spec9.spec @@ -1,5 +1,5 @@ { - "data_specification": { + "module_spec": { "module_name": "Spec2", "config_data": [ { "item_name": "item1", From 2884a118db89b1be50dd6ae57c0e80223c2e8bae Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 14:30:52 +0000 Subject: [PATCH 25/81] config_data existence check done in function git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@837 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/datadefinition.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/datadefinition.py index 3d4e1e75c0..5808cf88c2 100644 --- a/src/lib/config/python/isc/config/datadefinition.py +++ b/src/lib/config/python/isc/config/datadefinition.py @@ -60,16 +60,8 @@ class ModuleSpec: non-optional missing values. Set this to False if you want to validate only a part of a configuration tree (like a list of non-default values)""" - print("[XX] validate called with data:") - print(data) data_def = self.get_config_spec() - print("[XX] data def:") - print(data_def) - if 'config_data' not in data_def: - if errors: - errors.append("The is no config_data for this specification") - return False - return _validate_spec_list(data_def['config_data'], full, data, errors) + return _validate_spec_list(data_def, full, data, errors) def get_module_name(self): From a9f45d46754c68959ec5ba71d16bf30b448adb3d Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 16 Feb 2010 15:50:29 +0000 Subject: [PATCH 26/81] Remove the GCOV files with "make clean". git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@838 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/Makefile.am | 2 ++ src/bin/host/Makefile.am | 2 ++ src/lib/cc/cpp/Makefile.am | 2 ++ src/lib/config/cpp/Makefile.am | 2 ++ src/lib/exceptions/cpp/Makefile.am | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am index ccdf18d905..3b55a6b67a 100644 --- a/src/bin/auth/Makefile.am +++ b/src/bin/auth/Makefile.am @@ -2,6 +2,8 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_builddir)/src/lib -I$(top_buildd pkglibexecdir = $(libexecdir)/@PACKAGE@ +CLEANFILES = *.gcno *.gcda + pkglibexec_PROGRAMS = b10-auth b10_auth_SOURCES = auth_srv.cc auth_srv.h b10_auth_SOURCES += common.cc common.h diff --git a/src/bin/host/Makefile.am b/src/bin/host/Makefile.am index 6f41cc621d..e21540f5f8 100644 --- a/src/bin/host/Makefile.am +++ b/src/bin/host/Makefile.am @@ -1,5 +1,7 @@ AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_builddir)/src/lib/dns/cpp -I$(top_builddir)/include/dns/cpp -I$(top_builddir)/include -I$(top_srcdir)/ext +CLEANFILES = *.gcno *.gcda + bin_PROGRAMS = host host_SOURCES = host.cc host_LDADD = $(top_builddir)/src/lib/dns/cpp/.libs/libdns.a diff --git a/src/lib/cc/cpp/Makefile.am b/src/lib/cc/cpp/Makefile.am index 4b95415875..1747f8d326 100644 --- a/src/lib/cc/cpp/Makefile.am +++ b/src/lib/cc/cpp/Makefile.am @@ -3,6 +3,8 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror lib_LIBRARIES = libcc.a libcc_a_SOURCES = data.cc data.h session.cc session.h +CLEANFILES = *.gcno *.gcda + TESTS = if HAVE_GTEST TESTS += run_unittests diff --git a/src/lib/config/cpp/Makefile.am b/src/lib/config/cpp/Makefile.am index 572fb9fac4..f2a477adf5 100644 --- a/src/lib/config/cpp/Makefile.am +++ b/src/lib/config/cpp/Makefile.am @@ -3,6 +3,8 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror lib_LIBRARIES = libcfgclient.a libcfgclient_a_SOURCES = data_def.h data_def.cc ccsession.cc ccsession.h +CLEANFILES = *.gcno *.gcda + TESTS = if HAVE_GTEST TESTS += run_unittests diff --git a/src/lib/exceptions/cpp/Makefile.am b/src/lib/exceptions/cpp/Makefile.am index 1f45b3789b..ea8136cd83 100644 --- a/src/lib/exceptions/cpp/Makefile.am +++ b/src/lib/exceptions/cpp/Makefile.am @@ -2,6 +2,8 @@ lib_LTLIBRARIES = libexceptions.la libexceptions_la_SOURCES = exceptions.h exceptions.cc +CLEANFILES = *.gcno *.gcda + TESTS = if HAVE_GTEST TESTS += run_unittests From 3c021a3f9c753c39794eab54ea4f1ed056475bdd Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 16 Feb 2010 15:52:37 +0000 Subject: [PATCH 27/81] Make sure the final python script (as created by "make") is removed on a "make clean". The intermediate .py file is created by configure, so is already removed during the "make distclean" step. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@839 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/Makefile.am | 2 +- src/bin/cfgmgr/Makefile.am | 2 +- src/bin/cmdctl/Makefile.am | 2 +- src/bin/msgq/Makefile.am | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am index 9a7b0dc0b3..7904447a63 100644 --- a/src/bin/bind10/Makefile.am +++ b/src/bin/bind10/Makefile.am @@ -1,5 +1,5 @@ bin_SCRIPTS = bind10 -CLEANFILES = bind10.py +CLEANFILES = bind10 pkglibexecdir = $(libexecdir)/@PACKAGE@ diff --git a/src/bin/cfgmgr/Makefile.am b/src/bin/cfgmgr/Makefile.am index 10bddbfe44..3891b5cec3 100644 --- a/src/bin/cfgmgr/Makefile.am +++ b/src/bin/cfgmgr/Makefile.am @@ -2,7 +2,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@ pkglibexec_SCRIPTS = b10-cfgmgr -CLEANFILES = b10-cfgmgr.py +CLEANFILES = b10-cfgmgr b10_cfgmgrdir = @localstatedir@/@PACKAGE@ b10_cfgmgr_DATA = diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am index e707993fb9..5465a6bead 100644 --- a/src/bin/cmdctl/Makefile.am +++ b/src/bin/cmdctl/Makefile.am @@ -5,7 +5,7 @@ pkglibexec_SCRIPTS = b10-cmdctl b10_cmdctldir = $(DESTDIR)$(pkgdatadir) b10_cmdctl_DATA = passwd.csv b10-cmdctl.pem -CLEANFILES= b10-cmdctl.py +CLEANFILES= b10-cmdctl # TODO: does this need $$(DESTDIR) also? # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am index 002fb7a97f..957c4c1f2f 100644 --- a/src/bin/msgq/Makefile.am +++ b/src/bin/msgq/Makefile.am @@ -2,7 +2,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@ pkglibexec_SCRIPTS = msgq -CLEANFILES = msgq.py +CLEANFILES = msgq # TODO: does this need $$(DESTDIR) also? # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix From 275dbabd2de091044e17603a4a8bf34d3aea6371 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 20:12:52 +0000 Subject: [PATCH 28/81] renamed CCSession to ModuleCCSession renamed UIConfigData to UICCSession That last one is now a subclass of MultiConfigData, since half the functions it needed turned out to be nothing but passtroughs (next is to do the same for ModuleCCSession, only that will be a subclass of ConfigData, and make the two as similar as possible, from a certain point of view they to the same, albeit from another 'direction') git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@842 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10.py.in | 4 +- src/bin/bindctl/bindcmd.py | 2 +- src/lib/config/python/isc/config/ccsession.py | 100 ++++-- .../config/python/isc/config/cfgmgr_test.py | 4 +- .../config/python/isc/config/config_data.py | 312 +++--------------- .../python/isc/config/config_data_test.py | 2 +- 6 files changed, 131 insertions(+), 293 deletions(-) diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 95321d7b29..bda9c8647f 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -116,7 +116,7 @@ class BoB: print("[XX] handling new config:") print(new_config) errors = [] - if self.ccs.get_config_spec().get_module_spec().validate(False, new_config, errors): + if self.ccs.get_module_spec().validate(False, new_config, errors): print("[XX] new config validated") self.ccs.set_config(new_config) answer = isc.config.ccsession.create_answer(0) @@ -209,7 +209,7 @@ class BoB: time.sleep(1) if self.verbose: print("[XX] starting ccsession") - self.ccs = isc.config.CCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler) + self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler) self.ccs.start() if self.verbose: print("[XX] ccsession started") diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index ca1180cad1..88b2984e3b 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -86,7 +86,7 @@ class BindCmdInterpreter(Cmd): return False # Get all module information from cmd-ctrld - self.config_data = isc.config.UIConfigData(self) + self.config_data = isc.config.UIModuleCCSession(self) self.update_commands() self.cmdloop() except KeyboardInterrupt: diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index a39cc83ec4..a8d8aa5bad 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -21,27 +21,28 @@ # modeled after ccsession.h/cc 'protocol' changes here need to be # made there as well -"""This module provides the CCSession class, as well as a set of +"""This module provides the ModuleCCSession class, as well as a set of utility functions to create and parse messages related to commands and configuration""" from isc.cc import Session +from isc.config.config_data import ConfigData, MultiConfigData import isc -class CCSessionError(Exception): pass +class ModuleCCSessionError(Exception): pass def parse_answer(msg): """Returns a tuple (rcode, value), where value depends on the command that was called. If rcode != 0, value is a string containing an error message""" if 'result' not in msg: - raise CCSessionError("answer message does not contain 'result' element") + raise ModuleCCSessionError("answer message does not contain 'result' element") elif type(msg['result']) != list: - raise CCSessionError("wrong result type in answer message") + raise ModuleCCSessionError("wrong result type in answer message") elif len(msg['result']) < 1: - raise CCSessionError("empty result list in answer message") + raise ModuleCCSessionError("empty result list in answer message") elif type(msg['result'][0]) != int: - raise CCSessionError("wrong rcode type in answer message") + raise ModuleCCSessionError("wrong rcode type in answer message") else: if len(msg['result']) > 1: return msg['result'][0], msg['result'][1] @@ -54,28 +55,28 @@ def create_answer(rcode, arg = None): on what the command or option was. If rcode != 0, arg must be a string containing an error message""" if type(rcode) != int: - raise CCSessionError("rcode in create_answer() must be an integer") + raise ModuleCCSessionError("rcode in create_answer() must be an integer") if rcode != 0 and type(arg) != str: - raise CCSessionError("arg in create_answer for rcode != 0 must be a string describing the error") + raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error") if arg != None: return { 'result': [ rcode, arg ] } else: return { 'result': [ rcode ] } -class CCSession: +class ModuleCCSession: """This class maintains a connection to the command channel, as well as configuration options for modules. The module provides a specification file that contains the module name, configuration - options, and commands. It also gives the CCSession two callback + options, and commands. It also gives the ModuleCCSession two callback functions, one to call when there is a direct command to the module, and one to update the configuration run-time. These callbacks are called when 'check_command' is called on the - CCSession""" + ModuleCCSession""" def __init__(self, spec_file_name, config_handler, command_handler): - """Initialize a CCSession. This does *NOT* send the + """Initialize a ModuleCCSession. This does *NOT* send the specification and request the configuration yet. Use start() - for that once the CCSession has been initialized. + for that once the ModuleCCSession has been initialized. specfile_name is the path to the specification file config_handler and command_handler are callback functions, see set_config_handler and set_command_handler for more @@ -137,8 +138,6 @@ class CCSession: if msg: answer = None try: - print("[XX] got msg: ") - print(msg) if "config_update" in msg and self._config_handler: answer = self._config_handler(msg["config_update"]) if "command" in msg and self._command_handler: @@ -163,11 +162,8 @@ class CCSession: def __send_spec(self): """Sends the data specification to the configuration manager""" - print("[XX] send spec for " + self._module_name + " to ConfigManager") self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager") answer, env = self._session.group_recvmsg(False) - print("[XX] got answer from cfgmgr:") - print(answer) def __request_config(self): """Asks the configuration manager for the current configuration, and call the config handler if set""" @@ -175,11 +171,75 @@ class CCSession: answer, env = self._session.group_recvmsg(False) rcode, value = parse_answer(answer) if rcode == 0: - if self._config_data.get_module_spec().validate(False, value): + if value != None and self._config_data.get_module_spec().validate(False, value): self._config_data.set_local_config(value); if self._config_handler: self._config_handler(value) else: # log error print("Error requesting configuration: " + value) - + +class UIModuleCCSession(MultiConfigData): + """This class is used in a configuration user interface. It contains + specific functions for getting, displaying, and sending + configuration settings.""" + def __init__(self, conn): + MultiConfigData.__init__(self) + self._conn = conn + self.request_specifications() + self.request_current_config() + + def request_specifications(self): + # this step should be unnecessary but is the current way cmdctl returns stuff + # so changes are needed there to make this clean (we need a command to simply get the + # full specs for everything, including commands etc, not separate gets for that) + specs = self._conn.send_GET('/config_spec') + commands = self._conn.send_GET('/commands') + for module in specs.keys(): + cur_spec = { 'module_name': module } + if module in specs and specs[module]: + cur_spec['config_data'] = specs[module] + if module in commands and commands[module]: + cur_spec['commands'] = commands[module] + + self.set_specification(isc.config.ModuleSpec(cur_spec)) + + def request_current_config(self): + config = self._conn.send_GET('/config_data') + if 'version' not in config or config['version'] != 1: + raise Exception("Bad config version") + self.set_local_config(config) + + def add_value(self, identifier, value_str): + module_spec = self.find_spec_part(identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): + raise DataTypeError(identifier + " is not a list") + value = isc.cc.data.parse_value_str(value_str) + cur_list, status = self.get_value(identifier) + if not cur_list: + cur_list = [] + if value not in cur_list: + cur_list.append(value) + self.set_value(identifier, cur_list) + + def remove_value(self, identifier, value_str): + module_spec = find_spec(self.config.specification, identifier) + if (type(module_spec) != dict or "list_item_spec" not in module_spec): + raise DataTypeError(identifier + " is not a list") + value = parse_value_str(value_str) + check_type(module_spec, [value]) + cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) + if not cur_list: + cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) + if not cur_list: + cur_list = [] + if value in cur_list: + cur_list.remove(value) + set(self.config_changes, identifier, cur_list) + + def commit(self): + if self.get_local_changes(): + self._conn.send_POST('/ConfigManager/set_config', self.get_local_changes()) + # todo: check result + self.request_current_config() + self.clear_local_changes() diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index af7e6f18e7..733ca3287e 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -62,7 +62,7 @@ class TestConfigManagerData(unittest.TestCase): # # We can probably use a more general version of this # -class FakeCCSession: +class FakeModuleCCSession: def __init__(self): self.subscriptions = {} # each entry is of the form [ channel, instance, message ] @@ -106,7 +106,7 @@ class TestConfigManager(unittest.TestCase): def setUp(self): self.data_path = os.environ['CONFIG_TESTDATA_PATH'] - self.fake_session = FakeCCSession() + self.fake_session = FakeModuleCCSession() self.cm = ConfigManager(self.data_path, self.fake_session) self.name = "TestModule" self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec") diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index bcc68e32ca..871bb8f31d 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -14,9 +14,10 @@ # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# Class to store configuration data and data definition -# Used by the config manager and python modules that communicate -# with the configuration manager +# Classes to store configuration data and data specifications +# +# Used by the config manager, (python) modules, and UI's (those last +# two through the classes in ccsession) # @@ -173,19 +174,23 @@ class MultiConfigData: self._local_changes = {} def set_specification(self, spec): + """Add or update a ModuleSpec""" if type(spec) != isc.config.ModuleSpec: raise Exception("not a datadef") self._specifications[spec.get_module_name()] = spec def get_module_spec(self, module): + """Returns the ModuleSpec for the module with the given name""" if module in self._specifications: return self._specifications[module] else: return None def find_spec_part(self, identifier): - """returns the specification for the item at the given - identifier, or None if not found""" + """Returns the specification for the item at the given + identifier, or None if not found. The first part of the + identifier (up to the first /) is interpreted as the module + name.""" if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition("/") @@ -194,30 +199,52 @@ class MultiConfigData: except isc.cc.data.DataNotFoundError as dnfe: return None - def set_current_config(self, config): + # this function should only be called by __request_config + def __set_current_config(self, config): + """Replace the full current config values.""" self._current_config = config def get_current_config(self): - """The current config is a dict where the first level is + """Returns the current configuration as it is known by the + configuration manager. It is a dict where the first level is the module name, and the value is the config values for that module""" return self._current_config def get_local_changes(self): + """Returns the local config changes, i.e. those that have not + been committed yet and are not known by the configuration + manager or the modules.""" return self._local_changes def clear_local_changes(self): + """Reverts all local changes""" self._local_changes = {} def get_local_value(self, identifier): + """Returns a specific local (uncommitted) configuration value, + as specified by the identifier. If the local changes do not + contain a new setting for this identifier, or if the + identifier cannot be found, None is returned. See + get_value() for a general way to find a configuration value + """ return isc.cc.data.find_no_exc(self._local_changes, identifier) def get_current_value(self, identifier): - """Returns the current non-default value, or None if not set""" + """Returns the current non-default value as known by the + configuration manager, or None if it is not set. + See get_value() for a general way to find a configuration + value + """ return isc.cc.data.find_no_exc(self._current_config, identifier) def get_default_value(self, identifier): - """returns the default value, or None if there is no default""" + """Returns the default value for the given identifier as + specified by the module specification, or None if there is + no default or the identifier could not be found. + See get_value() for a general way to find a configuration + value + """ if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition("/") @@ -231,10 +258,12 @@ class MultiConfigData: return None def get_value(self, identifier): - """Returns a tuple containing value,status. Status is either - LOCAL, CURRENT, DEFAULT or NONE, corresponding to the - source of the value (local change, current setting, default - as specified by the specification, or not found at all).""" + """Returns a tuple containing value,status. + The value contains the configuration value for the given + identifier. The status reports where this value came from; + it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding + (local change, current setting, default as specified by the + specification, or not found at all).""" value = self.get_local_value(identifier) if value: return value, self.LOCAL @@ -253,8 +282,8 @@ class MultiConfigData: value: value of the entry if it is a string, int, double or bool modified: true if the value is a local change default: true if the value has been changed - Throws DataNotFoundError if the identifier is bad TODO: use the consts for those last ones + Throws DataNotFoundError if the identifier is bad """ result = [] if not identifier: @@ -327,8 +356,8 @@ class MultiConfigData: def set_value(self, identifier, value): """Set the local value at the given identifier to value""" spec_part = self.find_spec_part(identifier) - if check_type(spec_part, value): - isc.cc.data.set(self._local_changes, identifier, value) + check_type(spec_part, value) + isc.cc.data.set(self._local_changes, identifier, value) def get_config_item_list(self, identifier = None): """Returns a list of strings containing the item_names of @@ -343,254 +372,3 @@ class MultiConfigData: return self._specifications.keys() -class UIConfigData(): - """This class is used in a configuration user interface. It contains - specific functions for getting, displaying, and sending - configuration settings.""" - def __init__(self, conn): - self._conn = conn - self._data = MultiConfigData() - self.request_specifications() - self.request_current_config() - a,b = self._data.get_value("/Boss/some_string") - - def request_specifications(self): - # this step should be unnecessary but is the current way cmdctl returns stuff - # so changes are needed there to make this clean (we need a command to simply get the - # full specs for everything, including commands etc, not separate gets for that) - specs = self._conn.send_GET('/config_spec') - commands = self._conn.send_GET('/commands') - #print(specs) - #print(commands) - for module in specs.keys(): - cur_spec = { 'module_name': module } - if module in specs and specs[module]: - cur_spec['config_data'] = specs[module] - if module in commands and commands[module]: - cur_spec['commands'] = commands[module] - - self._data.set_specification(isc.config.ModuleSpec(cur_spec)) - - def request_current_config(self): - config = self._conn.send_GET('/config_data') - if 'version' not in config or config['version'] != 1: - raise Exception("Bad config version") - self._data.set_current_config(config) - - def get_value(self, identifier): - return self._data.get_value(identifier) - - def set_value(self, identifier, value): - return self._data.set_value(identifier, value); - - def add_value(self, identifier, value_str): - module_spec = self._data.find_spec_part(identifier) - if (type(module_spec) != dict or "list_item_spec" not in module_spec): - raise DataTypeError(identifier + " is not a list") - value = isc.cc.data.parse_value_str(value_str) - cur_list, status = self.get_value(identifier) - if not cur_list: - cur_list = [] - if value not in cur_list: - cur_list.append(value) - self.set_value(identifier, cur_list) - - def remove_value(self, identifier, value_str): - module_spec = find_spec(self.config.specification, identifier) - if (type(module_spec) != dict or "list_item_spec" not in module_spec): - raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(module_spec, [value]) - cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) - if not cur_list: - cur_list = [] - if value in cur_list: - cur_list.remove(value) - set(self.config_changes, identifier, cur_list) - - def get_value_maps(self, identifier = None): - return self._data.get_value_maps(identifier) - - def get_local_changes(self): - return self._data.get_local_changes() - - def commit(self): - self._conn.send_POST('/ConfigManager/set_config', self._data.get_local_changes()) - # todo: check result - self.request_current_config() - self._data.clear_local_changes() - - def get_config_item_list(self, identifier = None): - return self._data.get_config_item_list(identifier) - -# remove -class OUIConfigData(): - """This class is used in a configuration user interface. It contains - specific functions for getting, displaying, and sending - configuration settings.""" - def __init__(self, conn): - # the specs dict contains module: configdata elements - # these should all be replaced by the new stuff - module_spec = self.get_module_spec(conn) - self.config = module_spec - self.get_config_spec(conn) - self.config_changes = {} - # - self.config_ - self.specs = self.get_module_specs(conn) - - - def get_config_spec(self, conn): - data = conn.send_GET('/config_data') - - def send_changes(self, conn): - conn.send_POST('/ConfigManager/set_config', self.config_changes) - # Get latest config data - self.get_config_spec(conn) - self.config_changes = {} - - def get_module_spec(self, conn): - return conn.send_GET('/config_spec') - - def get_module_specs(self, conn): - specs = {} - allspecs = conn.send_GET('/config_spec') - - - def set(self, identifier, value): - # check against definition - spec = find_spec(identifier) - check_type(spec, value) - set(self.config_changes, identifier, value) - - def get_value(self, identifier): - """Returns a three-tuple, where the first item is the value - (or None), the second is a boolean specifying whether - the value is the default value, and the third is a boolean - specifying whether the value is an uncommitted change""" - value = isc.cc.data.find_no_exc(self.config_changes, identifier) - if value: - return value, False, True - value, default = self.config.get_value(identifier) - if value: - return value, default, False - return None, False, False - - def get_value_map_single(self, identifier, entry): - """Returns a single entry for a value_map, where the value is - not a part of a bigger map""" - result_part = {} - result_part['name'] = entry['item_name'] - result_part['type'] = entry['item_type'] - value, default, modified = self.get_value(identifier) - # should we check type and only set int, double, bool and string here? - result_part['value'] = value - result_part['default'] = default - result_part['modified'] = modified - return result_part - - def get_value_map(self, identifier, entry): - """Returns a single entry for a value_map, where the value is - a part of a bigger map""" - result_part = {} - result_part['name'] = entry['item_name'] - result_part['type'] = entry['item_type'] - value, default, modified = self.get_value(identifier + "/" + entry['item_name']) - # should we check type and only set int, double, bool and string here? - result_part['value'] = value - result_part['default'] = default - result_part['modified'] = modified - return result_part - - def get_value_maps(self, identifier = None): - """Returns a list of maps, containing the following values: - name: name of the entry (string) - type: string containing the type of the value (or 'module') - value: value of the entry if it is a string, int, double or bool - modified: true if the value is a local change - default: true if the value has been changed - Throws DataNotFoundError if the identifier is bad - """ - spec = find_spec(self.config, identifier) - result = [] - if type(spec) == dict: - # either the top-level list of modules or a spec map - if 'item_name' in spec: - result_part = self.get_value_map_single(identifier, spec) - if result_part['type'] == "list": - values = self.get_value(identifier)[0] - if values: - for value in values: - result_part2 = {} - li_spec = spec['list_item_spec'] - result_part2['name'] = li_spec['item_name'] - result_part2['value'] = value - result_part2['type'] = li_spec['item_type'] - result_part2['default'] = False - result_part2['modified'] = False - result.append(result_part2) - else: - result.append(result_part) - - else: - for name in spec: - result_part = {} - result_part['name'] = name - result_part['type'] = "module" - result_part['value'] = None - result_part['default'] = False - result_part['modified'] = False - result.append(result_part) - elif type(spec) == list: - for entry in spec: - if type(entry) == dict and 'item_name' in entry: - result.append(self.get_value_map(identifier, entry)) - return result - - def add(self, identifier, value_str): - module_spec = find_spec(self.config.specification, identifier) - if (type(module_spec) != dict or "list_item_spec" not in module_spec): - raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(module_spec, [value]) - cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) - if not cur_list: - cur_list = [] - if value not in cur_list: - cur_list.append(value) - set(self.config_changes, identifier, cur_list) - - def remove(self, identifier, value_str): - module_spec = find_spec(self.config.specification, identifier) - if (type(module_spec) != dict or "list_item_spec" not in module_spec): - raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(module_spec, [value]) - cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) - if not cur_list: - cur_list = [] - if value in cur_list: - cur_list.remove(value) - set(self.config_changes, identifier, cur_list) - - def set(self, identifier, value_str): - module_spec = find_spec(self.config.specification, identifier) - value = parse_value_str(value_str) - check_type(module_spec, value) - set(self.config_changes, identifier, value) - - def unset(self, identifier): - # todo: check whether the value is optional? - unset(self.config_changes, identifier) - - def revert(self): - self.config_changes = {} - - def commit(self, conn): - self.send_changes(conn) diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index fe27a7c10e..0364babd08 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -14,7 +14,7 @@ # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# Tests for the ConfigData and UIConfigData classes +# Tests for the ConfigData and MultiConfigData classes # import unittest From 4606f09458d2179fcd65eb9b27c505089297b33a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 20:52:40 +0000 Subject: [PATCH 29/81] ModuleCCSession is now a subclass of ConfigData git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@843 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10.py.in | 4 +- src/lib/config/python/isc/config/ccsession.py | 55 ++++++++++--------- .../config/python/isc/config/config_data.py | 4 +- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index bda9c8647f..61e3b90892 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -117,11 +117,9 @@ class BoB: print(new_config) errors = [] if self.ccs.get_module_spec().validate(False, new_config, errors): - print("[XX] new config validated") - self.ccs.set_config(new_config) + self.ccs.set_local_config(new_config) answer = isc.config.ccsession.create_answer(0) else: - print("[XX] new config validation failure") if len(errors) > 0: answer = isc.config.ccsession.create_answer(1, " ".join(errors)) else: diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index a8d8aa5bad..ebd8801a7b 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -21,9 +21,9 @@ # modeled after ccsession.h/cc 'protocol' changes here need to be # made there as well -"""This module provides the ModuleCCSession class, as well as a set of - utility functions to create and parse messages related to commands - and configuration""" +"""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""" from isc.cc import Session from isc.config.config_data import ConfigData, MultiConfigData @@ -63,7 +63,7 @@ def create_answer(rcode, arg = None): else: return { 'result': [ rcode ] } -class ModuleCCSession: +class ModuleCCSession(ConfigData): """This class maintains a connection to the command channel, as well as configuration options for modules. The module provides a specification file that contains the module name, configuration @@ -81,9 +81,10 @@ class ModuleCCSession: config_handler and command_handler are callback functions, see set_config_handler and set_command_handler for more information on their signatures.""" - data_definition = isc.config.module_spec_from_file(spec_file_name) - self._config_data = isc.config.config_data.ConfigData(data_definition) - self._module_name = data_definition.get_module_name() + module_spec = isc.config.module_spec_from_file(spec_file_name) + ConfigData.__init__(self, module_spec) + + self._module_name = module_spec.get_module_name() self.set_config_handler(config_handler) self.set_command_handler(command_handler) @@ -110,21 +111,6 @@ class ModuleCCSession: application can use it directly.""" return self._session - def set_config(self, new_config): - """Sets the current or non-default configuration""" - return self._config_data.set_local_config(new_config) - - def get_config(self): - """Returns the current or non-default configuration""" - return self._config_data.get_local_config() - - def get_full_config(self): - """Returns the current or non-default configuration""" - return self._config_data.get_full_config() - - def get_module_spec(self): - return self._config_data.get_module_spec() - def close(self): """Close the session to the command channel""" self._session.close() @@ -162,7 +148,7 @@ class ModuleCCSession: def __send_spec(self): """Sends the data specification to the configuration manager""" - self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager") + self._session.group_sendmsg({ "module_spec": self.get_module_spec().get_full_spec() }, "ConfigManager") answer, env = self._session.group_recvmsg(False) def __request_config(self): @@ -171,8 +157,8 @@ class ModuleCCSession: answer, env = self._session.group_recvmsg(False) rcode, value = parse_answer(answer) if rcode == 0: - if value != None and self._config_data.get_module_spec().validate(False, value): - self._config_data.set_local_config(value); + if value != None and self.get_module_spec().validate(False, value): + self.set_local_config(value); if self._config_handler: self._config_handler(value) else: @@ -182,14 +168,17 @@ class ModuleCCSession: class UIModuleCCSession(MultiConfigData): """This class is used in a configuration user interface. It contains specific functions for getting, displaying, and sending - configuration settings.""" + configuration settings through the b10-cmdctl module.""" def __init__(self, conn): + """Initialize a UIModuleCCSession. The conn object that is + passed must have send_GET and send_POST functions""" MultiConfigData.__init__(self) self._conn = conn self.request_specifications() self.request_current_config() def request_specifications(self): + """Request the module specifications from b10-cmdctl""" # this step should be unnecessary but is the current way cmdctl returns stuff # so changes are needed there to make this clean (we need a command to simply get the # full specs for everything, including commands etc, not separate gets for that) @@ -205,12 +194,17 @@ class UIModuleCCSession(MultiConfigData): self.set_specification(isc.config.ModuleSpec(cur_spec)) def request_current_config(self): + """Requests the current configuration from the configuration + manager through b10-cmdctl, and stores those as CURRENT""" config = self._conn.send_GET('/config_data') if 'version' not in config or config['version'] != 1: raise Exception("Bad config version") - self.set_local_config(config) + self._set_current_config(config) def add_value(self, identifier, value_str): + """Add a value to a configuration list. Raises a DataTypeError + if the value does not conform to the list_item_spec field + of the module config data specification""" module_spec = self.find_spec_part(identifier) if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") @@ -223,6 +217,11 @@ class UIModuleCCSession(MultiConfigData): self.set_value(identifier, cur_list) def remove_value(self, identifier, value_str): + """Remove a value from a configuration list. The value string + must be a string representation of the full item. Raises + a DataTypeError if the value at the identifier is not a list, + or if the given value_str does not match the list_item_spec + """ module_spec = find_spec(self.config.specification, identifier) if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") @@ -238,6 +237,8 @@ class UIModuleCCSession(MultiConfigData): set(self.config_changes, identifier, cur_list) def commit(self): + """Commit all local changes, send them through b10-cmdctl to + the configuration manager""" if self.get_local_changes(): self._conn.send_POST('/ConfigManager/set_config', self.get_local_changes()) # todo: check result diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 871bb8f31d..fc450df535 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -200,7 +200,7 @@ class MultiConfigData: return None # this function should only be called by __request_config - def __set_current_config(self, config): + def _set_current_config(self, config): """Replace the full current config values.""" self._current_config = config @@ -370,5 +370,3 @@ class MultiConfigData: return spec_name_list(spec, identifier + "/") else: return self._specifications.keys() - - From df4248e11eb7df43d84795c552cf6267d8ac9fd1 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 16 Feb 2010 21:11:38 +0000 Subject: [PATCH 30/81] some more documentation git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@844 e5f2f494-b856-4b98-b285-d166d9295462 --- .../config/python/isc/config/config_data.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index fc450df535..1ca0b5bc6a 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -13,26 +13,25 @@ # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# -# Classes to store configuration data and data specifications -# -# Used by the config manager, (python) modules, and UI's (those last -# two through the classes in ccsession) -# +""" +Classes to store configuration data and module specifications +Used by the config manager, (python) modules, and UI's (those last +two through the classes in ccsession) +""" import isc.cc.data import isc.config.datadefinition class ConfigDataError(Exception): pass -def check_type(specification, value): +def check_type(spec_part, value): """Returns true if the value is of the correct type given the specification part relevant for the value""" - if type(specification) == list: + if type(spec_part) == list: data_type = "list" else: - data_type = specification['item_type'] + data_type = spec_part['item_type'] if data_type == "integer" and type(value) != int: raise isc.cc.data.DataTypeError(str(value) + " is not an integer") @@ -47,7 +46,7 @@ def check_type(specification, value): raise isc.cc.data.DataTypeError(str(value) + " is not a list") else: for element in value: - check_type(specification['list_item_spec'], element) + check_type(spec_part['list_item_spec'], element) elif data_type == "map" and type(value) != dict: # todo: check types of map contents too raise isc.cc.data.DataTypeError(str(value) + " is not a map") @@ -115,12 +114,6 @@ class ConfigData: self.specification = specification self.data = {} - def get_item_list(self, identifier = None, recurse = False): - if identifier: - spec = find_spec(self.specification.get_config_spec(), identifier, recurse) - return spec_name_list(spec, identifier + "/") - return spec_name_list(self.specification.get_config_spec(), "", recurse) - def get_value(self, identifier): """Returns a tuple where the first item is the value at the given identifier, and the second item is a bool which is @@ -145,7 +138,22 @@ class ConfigData: """Returns the non-default config values in a dict""" return self.data; + def get_item_list(self, identifier = None, recurse = False): + """Returns a list of strings containing the full identifiers of + all 'sub'options at the given identifier. If recurse is True, + it will also add all identifiers of all children, if any""" + if identifier: + spec = find_spec(self.specification.get_config_spec(), identifier, recurse) + return spec_name_list(spec, identifier + "/") + return spec_name_list(self.specification.get_config_spec(), "", recurse) + def get_full_config(self): + """Returns a dict containing identifier: value elements, for + all configuration options for this module. If there is + a local setting, that will be used. Otherwise the value + will be the default as specified by the module specification. + If there is no default and no local setting, the value will + be None""" items = self.get_item_list(None, True) result = {} for item in items: @@ -153,12 +161,6 @@ class ConfigData: result[item] = value return result - #def get_identifiers(self): - # Returns a list containing all identifiers - - #def - - class MultiConfigData: """This class stores the datadefinitions, current non-default configuration values and 'local' (uncommitted) changes for From 42488e3ae48508be50726e438106f163edfe1d1d Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 17 Feb 2010 17:08:25 +0000 Subject: [PATCH 31/81] Fix typo in docstring. Remove "above" from a docstring as some aren't really "above". git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@848 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/msgq/msgq.py.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in index 33ae9f04a9..037b406bb8 100644 --- a/src/bin/msgq/msgq.py.in +++ b/src/bin/msgq/msgq.py.in @@ -215,7 +215,7 @@ class MsgQ: def process_command(self, fd, sock, routing, data): """Process a single command. This will split out into one of the - other functions, above.""" + other functions.""" print("[XX] got command: ") print(routing) cmd = routing["type"] @@ -251,7 +251,7 @@ class MsgQ: sock.send(msg) def newlname(self): - """Generate a unique conenction identifier for this socket. + """Generate a unique connection identifier for this socket. This is done by using an increasing counter and the current time.""" self.connection_counter += 1 From f14227c84475c3267455966b4b88ce51be7df014 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 17 Feb 2010 18:57:20 +0000 Subject: [PATCH 32/81] Add some more details. BIND 10 actually can install. TODO: change the ParkingLot examples to "b10-auth". git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@849 e5f2f494-b856-4b98-b285-d166d9295462 --- README | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/README b/README index fc27d0a072..23bea86431 100644 --- a/README +++ b/README @@ -16,6 +16,9 @@ Requires autoconf 2.59 or newer. Use automake-1.11 or better for working Python 3.1 tests. +Install with: + + make install TEST COVERAGE @@ -36,18 +39,25 @@ Doing code coverage tests: RUNNING -At the moment there is no install yet, you can run the bind10 parkinglot -server from the source tree: -./src/bin/bind10/bind10 +You can start the BIND 10 processes by running bind10 which is +installed to the sbin directory under the installation prefix. +The default location is: + + /usr/local/sbin/bind10 + +For development work, you can also run the bind10 services from the +source tree: + + ./src/bin/bind10/run_bind10.sh + +(Which will use the modules and configurations also from the source +tree.) The server will listen on port 5300 for DNS requests. - CONFIGURATION -Commands can be given through the tool bindctl; -cd src/bin/bindctl -sh bindctl +Commands can be given through the bindctl tool. The server must be running for bindctl to work. @@ -74,7 +84,7 @@ config commit: Commit all changes EXAMPLE SESSION -~> sh bindctl +~> bindctl > config show ParkingLot/ module > config show ParkingLot/ From ec83c0c7bbb7a670266bddcb578c557a88be10ae Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 17 Feb 2010 18:58:18 +0000 Subject: [PATCH 33/81] Install the daemon to sbin (not bin). git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@850 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am index 7904447a63..c0165456fd 100644 --- a/src/bin/bind10/Makefile.am +++ b/src/bin/bind10/Makefile.am @@ -1,4 +1,4 @@ -bin_SCRIPTS = bind10 +sbin_SCRIPTS = bind10 CLEANFILES = bind10 pkglibexecdir = $(libexecdir)/@PACKAGE@ From 6a3cff230b1af3aba437f47d805a735b1b66ed8f Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 17 Feb 2010 19:24:18 +0000 Subject: [PATCH 34/81] created a branch to complete the RRset class to be merged to trunk git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@851 e5f2f494-b856-4b98-b285-d166d9295462 From 50d6b031244c96d003dd702ffab62bf567faa019 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 17 Feb 2010 19:37:40 +0000 Subject: [PATCH 35/81] propset git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@852 e5f2f494-b856-4b98-b285-d166d9295462 From da9c7b09ab550f477804ffba4fcdd0272c7afc18 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 17 Feb 2010 19:39:08 +0000 Subject: [PATCH 36/81] Use Auth instead of ParkingLot for the module_name. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@853 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth.spec | 2 +- src/bin/bind10/bind10.py.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/auth/auth.spec b/src/bin/auth/auth.spec index 6c37bf375a..f91f4563d5 100644 --- a/src/bin/auth/auth.spec +++ b/src/bin/auth/auth.spec @@ -1,6 +1,6 @@ { "data_specification": { - "module_name": "ParkingLot" + "module_name": "Auth" } } diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index aecc95fc45..94a6d1832e 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -233,7 +233,7 @@ class BoB: cmd = { "command": ['shutdown']} self.cc_session.group_sendmsg(cmd, 'Boss', 'Cmd-Ctrld') self.cc_session.group_sendmsg(cmd, "Boss", "ConfigManager") - self.cc_session.group_sendmsg(cmd, "Boss", "ParkingLot") + self.cc_session.group_sendmsg(cmd, "Boss", "Auth") def stop_process(self, process): """Stop the given process, friendly-like.""" From ab794b8819396cbcf23b06a0ea117f3abcf14b3e Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 17 Feb 2010 19:42:16 +0000 Subject: [PATCH 37/81] checkpoint: minor cleanup git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@854 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rrset.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/dns/cpp/rrset.h b/src/lib/dns/cpp/rrset.h index 280fd273a1..f2540b370a 100644 --- a/src/lib/dns/cpp/rrset.h +++ b/src/lib/dns/cpp/rrset.h @@ -19,7 +19,6 @@ #include #include -#include #include @@ -73,6 +72,10 @@ typedef boost::shared_ptr RdataIteratorPtr; // by one)? ldns has ldns_rr_list_compare(), which takes // the latter approach (assuming the caller sorts the lists // beforehand?). +// - do we need to allow the user to remove specific Rdata? +/// Looking at the BIND9 code, don't see the strong need for this at the +/// moment. +/// class AbstractRRset { public: virtual ~AbstractRRset() {} @@ -92,10 +95,6 @@ public: /// once constructed, only TTL and the set of Rdata can be modified, /// so \c setTTL() is the only explicit setter method. /// - /// Do we need to allow the user to remove specific Rdata? - /// Looking at the BIND9 code, don't see the strong need for this at the - /// moment. - /// virtual void setTTL(const RRTTL& ttl) = 0; /// From 61cadb10c0046083ede36b222c1e80094d7e6322 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 09:24:49 +0000 Subject: [PATCH 38/81] renamed datadefinition.py to module_spec.py git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@864 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/__init__.py | 2 +- src/lib/config/python/isc/config/config_data.py | 6 +++--- src/lib/config/python/isc/config/config_data_test.py | 2 +- src/lib/config/python/isc/config/config_test.in | 4 +++- .../python/isc/config/{datadefinition.py => module_spec.py} | 0 .../config/{datadefinition_test.py => module_spec_test.py} | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) rename src/lib/config/python/isc/config/{datadefinition.py => module_spec.py} (100%) rename src/lib/config/python/isc/config/{datadefinition_test.py => module_spec_test.py} (99%) diff --git a/src/lib/config/python/isc/config/__init__.py b/src/lib/config/python/isc/config/__init__.py index 920f95bfa5..dcd7e65d01 100644 --- a/src/lib/config/python/isc/config/__init__.py +++ b/src/lib/config/python/isc/config/__init__.py @@ -1,3 +1,3 @@ from isc.config.ccsession import * from isc.config.config_data import * -from isc.config.datadefinition import * +from isc.config.module_spec import * diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 1ca0b5bc6a..9ce1b80821 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -21,7 +21,7 @@ two through the classes in ccsession) """ import isc.cc.data -import isc.config.datadefinition +import isc.config.module_spec class ConfigDataError(Exception): pass @@ -102,7 +102,7 @@ def spec_name_list(spec, prefix="", recurse=False): class ConfigData: - """This class stores the datadefinition and the current non-default + """This class stores the module specs and the current non-default config values. It provides functions to get the actual value or the default value if no non-default value has been set""" @@ -162,7 +162,7 @@ class ConfigData: return result class MultiConfigData: - """This class stores the datadefinitions, current non-default + """This class stores the module specs, current non-default configuration values and 'local' (uncommitted) changes for multiple modules""" LOCAL = 1 diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 0364babd08..a5e383a017 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -20,7 +20,7 @@ import unittest import os from isc.config.config_data import * -from isc.config.datadefinition import * +from isc.config.module_spec import * class TestConfigData(unittest.TestCase): def setUp(self): diff --git a/src/lib/config/python/isc/config/config_test.in b/src/lib/config/python/isc/config/config_test.in index 8ad1eaa8f7..708e2493be 100755 --- a/src/lib/config/python/isc/config/config_test.in +++ b/src/lib/config/python/isc/config/config_test.in @@ -12,7 +12,9 @@ CONFIG_TESTDATA_PATH=@abs_top_srcdir@/src/lib/config/testdata export CONFIG_TESTDATA_PATH cd ${BIND10_PATH} -${PYTHON_EXEC} -O ${CONFIG_PATH}/datadefinition_test.py $* +${PYTHON_EXEC} -O ${CONFIG_PATH}/config_data_test.py $* + +${PYTHON_EXEC} -O ${CONFIG_PATH}/module_spec_test.py $* ${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $* diff --git a/src/lib/config/python/isc/config/datadefinition.py b/src/lib/config/python/isc/config/module_spec.py similarity index 100% rename from src/lib/config/python/isc/config/datadefinition.py rename to src/lib/config/python/isc/config/module_spec.py diff --git a/src/lib/config/python/isc/config/datadefinition_test.py b/src/lib/config/python/isc/config/module_spec_test.py similarity index 99% rename from src/lib/config/python/isc/config/datadefinition_test.py rename to src/lib/config/python/isc/config/module_spec_test.py index 28eed4fc58..9ccaa310ac 100644 --- a/src/lib/config/python/isc/config/datadefinition_test.py +++ b/src/lib/config/python/isc/config/module_spec_test.py @@ -14,7 +14,7 @@ # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# Tests for the datadefinition module +# Tests for the module_spec module # import unittest From 29920cfb6a6c9d125242dd1805a5a993d1a11d47 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 10:11:15 +0000 Subject: [PATCH 39/81] documentation and tests for configuration manager git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@865 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 15 +++- src/lib/config/python/isc/config/cfgmgr.py | 57 ++++++++----- .../config/python/isc/config/cfgmgr_test.py | 83 ++++++++++++++++++- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index ebd8801a7b..3142ee6c37 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -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 diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index cc5a904946..ea9f9986bc 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -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 /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 diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 733ca3287e..0e9f442a4b 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -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) From 2143745c89e8dc3d93fb095bb1e9dae046ff8b29 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 10:38:48 +0000 Subject: [PATCH 40/81] docstrings git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@866 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/config_test.in | 1 - src/lib/config/python/isc/config/module_spec.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/config/python/isc/config/config_test.in b/src/lib/config/python/isc/config/config_test.in index 708e2493be..4ee795ec5a 100755 --- a/src/lib/config/python/isc/config/config_test.in +++ b/src/lib/config/python/isc/config/config_test.in @@ -17,4 +17,3 @@ ${PYTHON_EXEC} -O ${CONFIG_PATH}/config_data_test.py $* ${PYTHON_EXEC} -O ${CONFIG_PATH}/module_spec_test.py $* ${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $* - diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index 5808cf88c2..22ebe87287 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -25,9 +25,15 @@ import isc.cc.data # import that so we can check those types class ModuleSpecError(Exception): + """This exception is raised it the ModuleSpec fails to initialize + or if there is a failure or parse error reading the specification + file""" pass def module_spec_from_file(spec_file, check = True): + """Returns a ModuleSpec object defined by the file at spec_file. + If check is True, the contents are verified. If there is an error + in those contents, a ModuleSpecError is raised.""" module_spec = None if hasattr(spec_file, 'read'): module_spec = ast.literal_eval(spec_file.read(-1)) @@ -44,6 +50,10 @@ def module_spec_from_file(spec_file, check = True): class ModuleSpec: def __init__(self, module_spec, check = True): + """Initializes a ModuleSpec object from the specification in + the given module_spec (which must be a dict). If check is + True, the contents are verified. Raises a ModuleSpec error + if there is something wrong with the contents of the dict""". if type(module_spec) != dict: raise ModuleSpecError("module_spec is of type " + str(type(module_spec)) + ", not dict") if check: @@ -65,6 +75,8 @@ class ModuleSpec: def get_module_name(self): + """Returns a string containing the name of the module as + specified by the specification given at __init__""" return self._module_spec['module_name'] def get_full_spec(self): From c42d690782e24caf3f3795e2efa641eb52decb26 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 12:03:40 +0000 Subject: [PATCH 41/81] module description git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@867 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/module_spec.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index 22ebe87287..ef2ad25e92 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -13,10 +13,14 @@ # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# -# This class holds the data definition and validates data agains that -# definition. It is the python equivalent of data_def.h -# +"""Module Specifications + + A module specification holds the information about what configuration + a module can have, and what commands it understands. It provides + functions to read it from a .spec file, and to validate a given + set of data against the specification +""" + import ast import isc.cc.data From d66ddb055378ea77a3551e647891677f06f0576a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 12:20:55 +0000 Subject: [PATCH 42/81] rename DataDefinition[Error] to ModuleSpec[Error] git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@868 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/ccsession.cc | 8 +-- src/lib/config/cpp/ccsession.h | 2 +- src/lib/config/cpp/data_def.cc | 48 +++++++++++------ src/lib/config/cpp/data_def.h | 53 ++++++++++++------- src/lib/config/cpp/data_def_unittests.cc | 26 ++++----- .../config/python/isc/config/module_spec.py | 2 +- 6 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 8c88c2b6e3..1e86922eb9 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -45,9 +45,9 @@ using namespace std; using isc::data::Element; using isc::data::ElementPtr; -using isc::data::DataDefinition; +using isc::data::ModuleSpec; using isc::data::ParseError; -using isc::data::DataDefinitionError; +using isc::data::ModuleSpecError; void CommandSession::read_data_definition(const std::string& filename) { @@ -61,11 +61,11 @@ CommandSession::read_data_definition(const std::string& filename) { } try { - data_definition_ = DataDefinition(file, true); + data_definition_ = ModuleSpec(file, true); } catch (ParseError pe) { cout << "Error parsing definition file: " << pe.what() << endl; exit(1); - } catch (DataDefinitionError dde) { + } catch (ModuleSpecError dde) { cout << "Error reading definition file: " << dde.what() << endl; exit(1); } diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index a2136c81b5..46d86ed8e3 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -75,7 +75,7 @@ private: std::string module_name_; isc::cc::Session session_; - isc::data::DataDefinition data_definition_; + isc::data::ModuleSpec data_definition_; isc::data::ElementPtr config_; isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config); diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/data_def.cc index 2b716dccc6..5043c7253b 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/data_def.cc @@ -1,3 +1,17 @@ +// Copyright (C) 2010 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. #include "data_def.h" @@ -8,7 +22,7 @@ #include -// todo: add more context to thrown DataDefinitionErrors? +// todo: add more context to thrown ModuleSpecErrors? using namespace isc::data; @@ -51,7 +65,7 @@ getType_value(const std::string& type_name) { } else if (type_name == "any") { return Element::any; } else { - throw DataDefinitionError(type_name + " is not a valid type name"); + throw ModuleSpecError(type_name + " is not a valid type name"); } } @@ -62,13 +76,13 @@ check_leaf_item(const ElementPtr& spec, const std::string& name, Element::types if (spec->get(name)->getType() == type) { return; } else { - throw DataDefinitionError(name + " not of type " + getType_string(type)); + throw ModuleSpecError(name + " not of type " + getType_string(type)); } } else if (mandatory) { // todo: want parent item name, and perhaps some info about location // in list? or just catch and throw new... // or make this part non-throwing and check return value... - throw DataDefinitionError(name + " missing in " + spec->str()); + throw ModuleSpecError(name + " missing in " + spec->str()); } } @@ -99,7 +113,7 @@ check_config_item(const ElementPtr& spec) { static void check_config_item_list(const ElementPtr& spec) { if (spec->getType() != Element::list) { - throw DataDefinitionError("config_data is not a list of elements"); + throw ModuleSpecError("config_data is not a list of elements"); } BOOST_FOREACH(ElementPtr item, spec->listValue()) { check_config_item(item); @@ -116,7 +130,7 @@ check_command(const ElementPtr& spec) { static void check_command_list(const ElementPtr& spec) { if (spec->getType() != Element::list) { - throw DataDefinitionError("commands is not a list of elements"); + throw ModuleSpecError("commands is not a list of elements"); } BOOST_FOREACH(ElementPtr item, spec->listValue()) { check_command(item); @@ -137,27 +151,27 @@ check_data_specification(const ElementPtr& spec) { } // checks whether the given element is a valid data definition -// throws a DataDefinitionError if the specification is bad +// throws a ModuleSpecError if the specification is bad static void check_definition(const ElementPtr& def) { if (!def->contains("module_spec")) { - throw DataDefinitionError("Data specification does not contain module_spec element"); + throw ModuleSpecError("Data specification does not contain module_spec element"); } else { check_data_specification(def->get("module_spec")); } } -DataDefinition::DataDefinition(const std::string& file_name, +ModuleSpec::ModuleSpec(const std::string& file_name, const bool check) - throw(ParseError, DataDefinitionError) { + throw(ParseError, ModuleSpecError) { std::ifstream file; file.open(file_name.c_str()); if (!file) { std::stringstream errs; errs << "Error opening " << file_name << ": " << strerror(errno); - throw DataDefinitionError(errs.str()); + throw ModuleSpecError(errs.str()); } definition = Element::createFromString(file, file_name); @@ -166,8 +180,8 @@ DataDefinition::DataDefinition(const std::string& file_name, } } -DataDefinition::DataDefinition(std::istream& in, const bool check) - throw(ParseError, DataDefinitionError) { +ModuleSpec::ModuleSpec(std::istream& in, const bool check) + throw(ParseError, ModuleSpecError) { definition = Element::createFromString(in); // make sure the whole structure is complete and valid if (check) { @@ -210,7 +224,7 @@ check_type(ElementPtr spec, ElementPtr element) } bool -DataDefinition::validate_item(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data) { if (!check_type(spec, data)) { // we should do some proper error feedback here // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl; @@ -240,7 +254,7 @@ DataDefinition::validate_item(const ElementPtr spec, const ElementPtr data) { // spec is a map with item_name etc, data is a map bool -DataDefinition::validate_spec(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data) { std::string item_name = spec->get("item_name")->stringValue(); bool optional = spec->get("item_optional")->boolValue(); ElementPtr data_el; @@ -260,7 +274,7 @@ DataDefinition::validate_spec(const ElementPtr spec, const ElementPtr data) { // spec is a list of maps, data is a map bool -DataDefinition::validate_spec_list(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data) { ElementPtr cur_data_el; std::string cur_item_name; BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) { @@ -275,7 +289,7 @@ DataDefinition::validate_spec_list(const ElementPtr spec, const ElementPtr data) // this function does *not* check if the specification is in correct // form, we should do that in the constructor bool -DataDefinition::validate(const ElementPtr data) { +ModuleSpec::validate(const ElementPtr data) { ElementPtr spec = definition->find("module_spec/config_data"); return validate_spec_list(spec, data); } diff --git a/src/lib/config/cpp/data_def.h b/src/lib/config/cpp/data_def.h index 5d64f58f0e..15fc2727d0 100644 --- a/src/lib/config/cpp/data_def.h +++ b/src/lib/config/cpp/data_def.h @@ -1,5 +1,20 @@ -#ifndef _DATA_DEF_H -#define _DATA_DEF_H 1 +// Copyright (C) 2010 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. + +#ifndef _MODULE_SPEC_H +#define _MODULE_SPEC_H 1 #include @@ -8,22 +23,22 @@ namespace isc { namespace data { /// - /// A standard DataDefinition exception that is thrown when a + /// A standard ModuleSpec exception that is thrown when a /// specification is not in the correct form. /// /// TODO: use jinmei's exception class as a base and not c_str in /// what() there - class DataDefinitionError : public std::exception { + class ModuleSpecError : public std::exception { public: - DataDefinitionError(std::string m = "Data definition is invalid") : msg(m) {} - ~DataDefinitionError() throw() {} + ModuleSpecError(std::string m = "Data definition is invalid") : msg(m) {} + ~ModuleSpecError() throw() {} const char* what() const throw() { return msg.c_str(); } private: std::string msg; }; /// - /// The \c DataDefinition class holds a data specification. + /// The \c ModuleSpec class holds a data specification. /// Each module should have a .spec file containing the specification /// for configuration and commands for that module. /// This class holds that specification, and provides a function to @@ -32,36 +47,36 @@ namespace isc { namespace data { /// /// The form of the specification is described in doc/ (TODO) /// - class DataDefinition { + class ModuleSpec { public: - explicit DataDefinition() {}; - /// Create a \c DataDefinition instance with the given data as + explicit ModuleSpec() {}; + /// Create a \c ModuleSpec instance with the given data as /// the specification /// \param e The Element containing the data specification - explicit DataDefinition(ElementPtr e) : definition(e) {}; + explicit ModuleSpec(ElementPtr e) : definition(e) {}; - /// Creates a \c DataDefinition instance from the contents + /// Creates a \c ModuleSpec instance from the contents /// of the file given by file_name. /// If check is true, and the definition is not of the correct - /// form, a DataDefinitionError is thrown. If the file could + /// form, a ModuleSpecError is thrown. If the file could /// not be parse, a ParseError is thrown. /// \param file_name The file to be opened and parsed /// \param check If true, the data definition in the file is /// checked to be of the correct form - DataDefinition(const std::string& file_name, const bool check = true) - throw(ParseError, DataDefinitionError); + ModuleSpec(const std::string& file_name, const bool check = true) + throw(ParseError, ModuleSpecError); // todo: make check default false, or leave out option completely and always check? - /// Creates a \c DataDefinition instance from the given input + /// Creates a \c ModuleSpec instance from the given input /// stream that contains the contents of a .spec file. /// If check is true, and the definition is not of - /// the correct form, a DataDefinitionError is thrown. If the + /// the correct form, a ModuleSpecError is thrown. If the /// file could not be parsed, a ParseError is thrown. /// \param in The std::istream containing the .spec file data /// \param check If true, the data definition is checked to be /// of the correct form - explicit DataDefinition(std::istream& in, const bool check = true) - throw(ParseError, DataDefinitionError); + explicit ModuleSpec(std::istream& in, const bool check = true) + throw(ParseError, ModuleSpecError); /// Returns the base \c element of the data definition contained /// by this instance diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/data_def_unittests.cc index 707f59adbe..45c16338fb 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/data_def_unittests.cc @@ -34,23 +34,23 @@ data_def_error(const std::string& file, const std::string& error2 = "", const std::string& error3 = "") { - EXPECT_THROW(DataDefinition(specfile(file)), DataDefinitionError); + EXPECT_THROW(ModuleSpec(specfile(file)), ModuleSpecError); try { - DataDefinition dd = DataDefinition(specfile(file)); - } catch (DataDefinitionError dde) { + ModuleSpec dd = ModuleSpec(specfile(file)); + } catch (ModuleSpecError dde) { std::string ddew = dde.what(); EXPECT_EQ(error1 + error2 + error3, ddew); } } -TEST(DataDefinition, ReadingSpecfiles) { +TEST(ModuleSpec, ReadingSpecfiles) { // Tests whether we can open specfiles and if we get the // right parse errors - DataDefinition dd = DataDefinition(specfile("spec1.spec")); + ModuleSpec dd = ModuleSpec(specfile("spec1.spec")); EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); - dd = DataDefinition(specfile("spec2.spec")); + dd = ModuleSpec(specfile("spec2.spec")); EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("config_data")->size(), 6); data_def_error("doesnotexist", @@ -60,13 +60,13 @@ TEST(DataDefinition, ReadingSpecfiles) { std::ifstream file; file.open(specfile("spec1.spec").c_str()); - dd = DataDefinition(file); + dd = ModuleSpec(file); EXPECT_EQ(dd.getDefinition()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); } -TEST(DataDefinition, SpecfileItems) { +TEST(ModuleSpec, SpecfileItems) { data_def_error("spec3.spec", "item_name missing in {\"item_default\": 1, \"item_optional\": False, \"item_type\": \"integer\"}"); data_def_error("spec4.spec", @@ -91,7 +91,7 @@ TEST(DataDefinition, SpecfileItems) { "badname is not a valid type name"); } -TEST(DataDefinition, SpecfileConfigData) +TEST(ModuleSpec, SpecfileConfigData) { data_def_error("spec7.spec", "module_name missing in {}"); @@ -103,7 +103,7 @@ TEST(DataDefinition, SpecfileConfigData) "commands is not a list of elements"); } -TEST(DataDefinition, SpecfileCommands) +TEST(ModuleSpec, SpecfileCommands) { data_def_error("spec17.spec", "command_name missing in {\"command_args\": [ {\"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": False, \"item_type\": \"string\"} ], \"command_description\": \"Print the given message to stdout\"}"); @@ -120,7 +120,7 @@ TEST(DataDefinition, SpecfileCommands) } bool -data_test(DataDefinition dd, const std::string& data_file_name) +data_test(ModuleSpec dd, const std::string& data_file_name) { std::ifstream data_file; @@ -131,8 +131,8 @@ data_test(DataDefinition dd, const std::string& data_file_name) return dd.validate(data); } -TEST(DataDefinition, DataValidation) { - DataDefinition dd = DataDefinition(specfile("spec22.spec")); +TEST(ModuleSpec, DataValidation) { + ModuleSpec dd = ModuleSpec(specfile("spec22.spec")); EXPECT_TRUE(data_test(dd, "data22_1.data")); EXPECT_FALSE(data_test(dd, "data22_2.data")); diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index ef2ad25e92..56cfb9ccb7 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -57,7 +57,7 @@ class ModuleSpec: """Initializes a ModuleSpec object from the specification in the given module_spec (which must be a dict). If check is True, the contents are verified. Raises a ModuleSpec error - if there is something wrong with the contents of the dict""". + if there is something wrong with the contents of the dict""" if type(module_spec) != dict: raise ModuleSpecError("module_spec is of type " + str(type(module_spec)) + ", not dict") if check: From b5eff6e5bd25045b1c76c039ed88426a618e4f70 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 14:11:43 +0000 Subject: [PATCH 43/81] sync cpp part with python part (similar functionality and function/class names) git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@869 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/main.cc | 2 +- src/bin/parkinglot/main.cc | 2 +- src/lib/config/cpp/ccsession.cc | 20 +++---- src/lib/config/cpp/ccsession.h | 18 +++--- src/lib/config/cpp/data_def.cc | 70 ++++++++++++++++++------ src/lib/config/cpp/data_def.h | 50 +++++++++++------ src/lib/config/cpp/data_def_unittests.cc | 6 +- 7 files changed, 110 insertions(+), 58 deletions(-) diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 511ff8b77e..85e22723a7 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -106,7 +106,7 @@ main(int argc, char* argv[]) { } else { specfile = std::string(AUTH_SPECFILE_LOCATION); } - CommandSession cs = CommandSession(specfile, + ModuleCCSession cs = ModuleCCSession(specfile, my_config_handler, my_command_handler); diff --git a/src/bin/parkinglot/main.cc b/src/bin/parkinglot/main.cc index b547c795a1..2b5086200c 100644 --- a/src/bin/parkinglot/main.cc +++ b/src/bin/parkinglot/main.cc @@ -110,7 +110,7 @@ main(int argc, char* argv[]) { } else { specfile = std::string(PARKINGLOT_SPECFILE_LOCATION); } - CommandSession cs = CommandSession(specfile, my_config_handler, my_command_handler); + ModuleCCSession cs = ModuleCCSession(specfile, my_config_handler, my_command_handler); // main server loop fd_set fds; diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 1e86922eb9..121c9a3644 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -50,7 +50,7 @@ using isc::data::ParseError; using isc::data::ModuleSpecError; void -CommandSession::read_data_definition(const std::string& filename) { +ModuleCCSession::read_module_specification(const std::string& filename) { std::ifstream file; // this file should be declared in a @something@ directive @@ -61,27 +61,27 @@ CommandSession::read_data_definition(const std::string& filename) { } try { - data_definition_ = ModuleSpec(file, true); + module_specification_ = ModuleSpec(file, true); } catch (ParseError pe) { - cout << "Error parsing definition file: " << pe.what() << endl; + cout << "Error parsing module specification file: " << pe.what() << endl; exit(1); } catch (ModuleSpecError dde) { - cout << "Error reading definition file: " << dde.what() << endl; + cout << "Error reading module specification file: " << dde.what() << endl; exit(1); } file.close(); } -CommandSession::CommandSession(std::string spec_file_name, +ModuleCCSession::ModuleCCSession(std::string spec_file_name, isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config), isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command) ) throw (isc::cc::SessionError): session_(isc::cc::Session()) { - read_data_definition(spec_file_name); + read_module_specification(spec_file_name); sleep(1); - module_name_ = data_definition_.getDefinition()->get("module_spec")->get("module_name")->stringValue(); + module_name_ = module_specification_.getFullSpec()->get("module_spec")->get("module_name")->stringValue(); config_handler_ = config_handler; command_handler_ = command_handler; @@ -96,7 +96,7 @@ CommandSession::CommandSession(std::string spec_file_name, //session_.subscribe("Boss", "*"); //session_.subscribe("statistics", "*"); // send the data specification - session_.group_sendmsg(data_definition_.getDefinition(), "ConfigManager"); + session_.group_sendmsg(module_specification_.getFullSpec(), "ConfigManager"); session_.group_recvmsg(env, answer, false); // get any stored configuration from the manager @@ -116,13 +116,13 @@ CommandSession::CommandSession(std::string spec_file_name, } int -CommandSession::getSocket() +ModuleCCSession::getSocket() { return (session_.getSocket()); } int -CommandSession::check_command() +ModuleCCSession::check_command() { cout << "[XX] check for command" << endl; ElementPtr cmd, routing, data; diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index 46d86ed8e3..53fc10460d 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -23,20 +23,20 @@ #include #include -class CommandSession { +class ModuleCCSession { public: /** * Initialize a config/command session * @param module_name: The name of this module. This is not a * reference because we expect static strings * to be passed here. - * @param spec_file_name: The name of the file containing the data - * definition. + * @param spec_file_name: The name of the file containing the + * module specification. */ - CommandSession(std::string spec_file_name, - isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config) = NULL, - isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command) = NULL - ) throw (isc::cc::SessionError); + ModuleCCSession(std::string spec_file_name, + isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config) = NULL, + isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command) = NULL + ) throw (isc::cc::SessionError); int getSocket(); /** @@ -71,11 +71,11 @@ public: void set_command_handler(isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)) { command_handler_ = command_handler; }; private: - void read_data_definition(const std::string& filename); + void read_module_specification(const std::string& filename); std::string module_name_; isc::cc::Session session_; - isc::data::ModuleSpec data_definition_; + isc::data::ModuleSpec module_specification_; isc::data::ElementPtr config_; isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config); diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/data_def.cc index 5043c7253b..179d5e8be7 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/data_def.cc @@ -26,6 +26,10 @@ using namespace isc::data; +// +// static functions +// + // todo: is there a direct way to get a std::string from an enum label? static std::string getType_string(Element::types type) @@ -98,7 +102,7 @@ check_config_item(const ElementPtr& spec) { !spec->get("item_optional")->boolValue() ); - // if list, check the list definition + // if list, check the list specification if (getType_value(spec->get("item_type")->stringValue()) == Element::list) { check_leaf_item(spec, "list_item_spec", Element::map, true); check_config_item(spec->get("list_item_spec")); @@ -150,10 +154,10 @@ check_data_specification(const ElementPtr& spec) { } } -// checks whether the given element is a valid data definition +// checks whether the given element is a valid module specification // throws a ModuleSpecError if the specification is bad static void -check_definition(const ElementPtr& def) +check_module_specification(const ElementPtr& def) { if (!def->contains("module_spec")) { throw ModuleSpecError("Data specification does not contain module_spec element"); @@ -162,6 +166,10 @@ check_definition(const ElementPtr& def) } } +// +// Public functions +// + ModuleSpec::ModuleSpec(const std::string& file_name, const bool check) throw(ParseError, ModuleSpecError) { @@ -174,21 +182,60 @@ ModuleSpec::ModuleSpec(const std::string& file_name, throw ModuleSpecError(errs.str()); } - definition = Element::createFromString(file, file_name); + module_specification = Element::createFromString(file, file_name); if (check) { - check_definition(definition); + check_module_specification(module_specification); } } + ModuleSpec::ModuleSpec(std::istream& in, const bool check) throw(ParseError, ModuleSpecError) { - definition = Element::createFromString(in); + module_specification = Element::createFromString(in); // make sure the whole structure is complete and valid if (check) { - check_definition(definition); + check_module_specification(module_specification); } } +const ElementPtr +ModuleSpec::getCommandsSpec() +{ + if (module_specification->contains("commands")) { + return module_specification->get("commands"); + } else { + return ElementPtr(); + } +} + +const ElementPtr +ModuleSpec::getConfigSpec() +{ + if (module_specification->contains("config_data")) { + return module_specification->get("config_data"); + } else { + return ElementPtr(); + } +} + +const std::string +ModuleSpec::getModuleName() +{ + return module_specification->get("module_name")->stringValue(); +} + +bool +ModuleSpec::validate(const ElementPtr data) +{ + ElementPtr spec = module_specification->find("module_spec/config_data"); + return validate_spec_list(spec, data); +} + + +// +// private functions +// + // // helper functions for validation // @@ -285,12 +332,3 @@ ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data) { return true; } -// TODO -// this function does *not* check if the specification is in correct -// form, we should do that in the constructor -bool -ModuleSpec::validate(const ElementPtr data) { - ElementPtr spec = definition->find("module_spec/config_data"); - return validate_spec_list(spec, data); -} - diff --git a/src/lib/config/cpp/data_def.h b/src/lib/config/cpp/data_def.h index 15fc2727d0..f3b3d5d70b 100644 --- a/src/lib/config/cpp/data_def.h +++ b/src/lib/config/cpp/data_def.h @@ -30,7 +30,7 @@ namespace isc { namespace data { /// what() there class ModuleSpecError : public std::exception { public: - ModuleSpecError(std::string m = "Data definition is invalid") : msg(m) {} + ModuleSpecError(std::string m = "Module specification is invalid") : msg(m) {} ~ModuleSpecError() throw() {} const char* what() const throw() { return msg.c_str(); } private: @@ -53,38 +53,52 @@ namespace isc { namespace data { /// Create a \c ModuleSpec instance with the given data as /// the specification /// \param e The Element containing the data specification - explicit ModuleSpec(ElementPtr e) : definition(e) {}; + explicit ModuleSpec(ElementPtr e) : module_specification(e) {}; /// Creates a \c ModuleSpec instance from the contents /// of the file given by file_name. - /// If check is true, and the definition is not of the correct - /// form, a ModuleSpecError is thrown. If the file could - /// not be parse, a ParseError is thrown. + /// If check is true, and the module specification is not of + /// the correct form, a ModuleSpecError is thrown. If the file + /// could not be parse, a ParseError is thrown. /// \param file_name The file to be opened and parsed - /// \param check If true, the data definition in the file is - /// checked to be of the correct form + /// \param check If true, the module specification in the file + /// is checked to be of the correct form ModuleSpec(const std::string& file_name, const bool check = true) throw(ParseError, ModuleSpecError); - // todo: make check default false, or leave out option completely and always check? /// Creates a \c ModuleSpec instance from the given input /// stream that contains the contents of a .spec file. - /// If check is true, and the definition is not of + /// If check is true, and the module specification is not of /// the correct form, a ModuleSpecError is thrown. If the /// file could not be parsed, a ParseError is thrown. /// \param in The std::istream containing the .spec file data - /// \param check If true, the data definition is checked to be - /// of the correct form + /// \param check If true, the module specification is checked + /// to be of the correct form explicit ModuleSpec(std::istream& in, const bool check = true) throw(ParseError, ModuleSpecError); - /// Returns the base \c element of the data definition contained - /// by this instance - /// \return ElementPtr Shared pointer to the data definition - const ElementPtr getDefinition() { return definition; }; + /// Returns the commands part of the specification as an + /// ElementPtr, returns an empty ElementPtr if there is none + /// \return ElementPtr Shared pointer to the commands + /// part of the specification + const ElementPtr getCommandsSpec(); + + /// Returns the configuration part of the specification as an + /// ElementPtr + /// \return ElementPtr Shared pointer to the configuration + /// part of the specification + const ElementPtr getConfigSpec(); + + /// Returns the full module specification as an ElementPtr + /// \return ElementPtr Shared pointer to the specification + const ElementPtr getFullSpec() { return module_specification; }; + + /// Returns the module name as specified by the specification + const std::string getModuleName(); + // returns true if the given element conforms to this data - // definition scheme - /// Validates the given data for this specification. + // configuration specification + /// Validates the given configuration data for this specification. /// \param data The base \c Element of the data to check /// \return true if the data conforms to the specification, /// false otherwise. @@ -95,7 +109,7 @@ namespace isc { namespace data { bool validate_spec(const ElementPtr spec, const ElementPtr data); bool validate_spec_list(const ElementPtr spec, const ElementPtr data); - ElementPtr definition; + ElementPtr module_specification; }; } } diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/data_def_unittests.cc index 45c16338fb..42f3b6653b 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/data_def_unittests.cc @@ -47,11 +47,11 @@ TEST(ModuleSpec, ReadingSpecfiles) { // Tests whether we can open specfiles and if we get the // right parse errors ModuleSpec dd = ModuleSpec(specfile("spec1.spec")); - EXPECT_EQ(dd.getDefinition()->get("module_spec") + EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); dd = ModuleSpec(specfile("spec2.spec")); - EXPECT_EQ(dd.getDefinition()->get("module_spec") + EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("config_data")->size(), 6); data_def_error("doesnotexist", "Error opening ", @@ -61,7 +61,7 @@ TEST(ModuleSpec, ReadingSpecfiles) { std::ifstream file; file.open(specfile("spec1.spec").c_str()); dd = ModuleSpec(file); - EXPECT_EQ(dd.getDefinition()->get("module_spec") + EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); } From 3cd4c9d2c350e2a65d46a6df90379b0c8310111f Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 14:44:48 +0000 Subject: [PATCH 44/81] ModuleSpec initializer not takes ElementPtr instead of file name, for creation from file name or ifstream there are the non-member moduleSpecFromFile functions now git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@870 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/ccsession.cc | 3 +- src/lib/config/cpp/data_def.cc | 58 ++++++++++++++---------- src/lib/config/cpp/data_def.h | 48 ++++++++++---------- src/lib/config/cpp/data_def_unittests.cc | 12 ++--- 4 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 121c9a3644..5249f40e0c 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -48,6 +48,7 @@ using isc::data::ElementPtr; using isc::data::ModuleSpec; using isc::data::ParseError; using isc::data::ModuleSpecError; +using namespace isc::data; void ModuleCCSession::read_module_specification(const std::string& filename) { @@ -61,7 +62,7 @@ ModuleCCSession::read_module_specification(const std::string& filename) { } try { - module_specification_ = ModuleSpec(file, true); + module_specification_ = moduleSpecFromFile(file, true); } catch (ParseError pe) { cout << "Error parsing module specification file: " << pe.what() << endl; exit(1); diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/data_def.cc index 179d5e8be7..c7eb3efdda 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/data_def.cc @@ -24,7 +24,8 @@ // todo: add more context to thrown ModuleSpecErrors? -using namespace isc::data; +namespace isc { +namespace data { // // static functions @@ -170,29 +171,12 @@ check_module_specification(const ElementPtr& def) // Public functions // -ModuleSpec::ModuleSpec(const std::string& file_name, - const bool check) - throw(ParseError, ModuleSpecError) { - std::ifstream file; - - file.open(file_name.c_str()); - if (!file) { - std::stringstream errs; - errs << "Error opening " << file_name << ": " << strerror(errno); - throw ModuleSpecError(errs.str()); - } - - module_specification = Element::createFromString(file, file_name); - if (check) { - check_module_specification(module_specification); - } -} - - -ModuleSpec::ModuleSpec(std::istream& in, const bool check) - throw(ParseError, ModuleSpecError) { - module_specification = Element::createFromString(in); - // make sure the whole structure is complete and valid +ModuleSpec::ModuleSpec(ElementPtr module_spec_element, + const bool check) + throw(ModuleSpecError) + +{ + module_specification = module_spec_element; if (check) { check_module_specification(module_specification); } @@ -231,6 +215,30 @@ ModuleSpec::validate(const ElementPtr data) return validate_spec_list(spec, data); } +ModuleSpec +moduleSpecFromFile(const std::string& file_name, const bool check) + throw(ParseError, ModuleSpecError) +{ + std::ifstream file; + + file.open(file_name.c_str()); + if (!file) { + std::stringstream errs; + errs << "Error opening " << file_name << ": " << strerror(errno); + throw ModuleSpecError(errs.str()); + } + + ElementPtr module_spec_element = Element::createFromString(file, file_name); + return ModuleSpec(module_spec_element, check); +} + +ModuleSpec +moduleSpecFromFile(std::ifstream& in, const bool check) + throw(ParseError, ModuleSpecError) { + ElementPtr module_spec_element = Element::createFromString(in); + return ModuleSpec(module_spec_element, check); +} + // // private functions @@ -332,3 +340,5 @@ ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data) { return true; } +} +} diff --git a/src/lib/config/cpp/data_def.h b/src/lib/config/cpp/data_def.h index f3b3d5d70b..5f76d97f84 100644 --- a/src/lib/config/cpp/data_def.h +++ b/src/lib/config/cpp/data_def.h @@ -53,29 +53,8 @@ namespace isc { namespace data { /// Create a \c ModuleSpec instance with the given data as /// the specification /// \param e The Element containing the data specification - explicit ModuleSpec(ElementPtr e) : module_specification(e) {}; - - /// Creates a \c ModuleSpec instance from the contents - /// of the file given by file_name. - /// If check is true, and the module specification is not of - /// the correct form, a ModuleSpecError is thrown. If the file - /// could not be parse, a ParseError is thrown. - /// \param file_name The file to be opened and parsed - /// \param check If true, the module specification in the file - /// is checked to be of the correct form - ModuleSpec(const std::string& file_name, const bool check = true) - throw(ParseError, ModuleSpecError); - - /// Creates a \c ModuleSpec instance from the given input - /// stream that contains the contents of a .spec file. - /// If check is true, and the module specification is not of - /// the correct form, a ModuleSpecError is thrown. If the - /// file could not be parsed, a ParseError is thrown. - /// \param in The std::istream containing the .spec file data - /// \param check If true, the module specification is checked - /// to be of the correct form - explicit ModuleSpec(std::istream& in, const bool check = true) - throw(ParseError, ModuleSpecError); + explicit ModuleSpec(ElementPtr e, const bool check = true) + throw(ModuleSpecError); /// Returns the commands part of the specification as an /// ElementPtr, returns an empty ElementPtr if there is none @@ -112,6 +91,29 @@ namespace isc { namespace data { ElementPtr module_specification; }; + /// Creates a \c ModuleSpec instance from the contents + /// of the file given by file_name. + /// If check is true, and the module specification is not of + /// the correct form, a ModuleSpecError is thrown. If the file + /// could not be parse, a ParseError is thrown. + /// \param file_name The file to be opened and parsed + /// \param check If true, the module specification in the file + /// is checked to be of the correct form + ModuleSpec + moduleSpecFromFile(const std::string& file_name, const bool check = true) + throw(ParseError, ModuleSpecError); + + /// Creates a \c ModuleSpec instance from the given input + /// stream that contains the contents of a .spec file. + /// If check is true, and the module specification is not of + /// the correct form, a ModuleSpecError is thrown. If the + /// file could not be parsed, a ParseError is thrown. + /// \param in The std::istream containing the .spec file data + /// \param check If true, the module specification is checked + /// to be of the correct form + ModuleSpec + moduleSpecFromFile(std::ifstream& in, const bool check = true) + throw(ParseError, ModuleSpecError); } } #endif // _DATA_DEF_H diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/data_def_unittests.cc index 42f3b6653b..6f5b138c21 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/data_def_unittests.cc @@ -34,9 +34,9 @@ data_def_error(const std::string& file, const std::string& error2 = "", const std::string& error3 = "") { - EXPECT_THROW(ModuleSpec(specfile(file)), ModuleSpecError); + EXPECT_THROW(moduleSpecFromFile(specfile(file)), ModuleSpecError); try { - ModuleSpec dd = ModuleSpec(specfile(file)); + ModuleSpec dd = moduleSpecFromFile(specfile(file)); } catch (ModuleSpecError dde) { std::string ddew = dde.what(); EXPECT_EQ(error1 + error2 + error3, ddew); @@ -46,11 +46,11 @@ data_def_error(const std::string& file, TEST(ModuleSpec, ReadingSpecfiles) { // Tests whether we can open specfiles and if we get the // right parse errors - ModuleSpec dd = ModuleSpec(specfile("spec1.spec")); + ModuleSpec dd = moduleSpecFromFile(specfile("spec1.spec")); EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); - dd = ModuleSpec(specfile("spec2.spec")); + dd = moduleSpecFromFile(specfile("spec2.spec")); EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("config_data")->size(), 6); data_def_error("doesnotexist", @@ -60,7 +60,7 @@ TEST(ModuleSpec, ReadingSpecfiles) { std::ifstream file; file.open(specfile("spec1.spec").c_str()); - dd = ModuleSpec(file); + dd = moduleSpecFromFile(file); EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("module_name") ->stringValue(), "Spec1"); @@ -132,7 +132,7 @@ data_test(ModuleSpec dd, const std::string& data_file_name) } TEST(ModuleSpec, DataValidation) { - ModuleSpec dd = ModuleSpec(specfile("spec22.spec")); + ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec")); EXPECT_TRUE(data_test(dd, "data22_1.data")); EXPECT_FALSE(data_test(dd, "data22_2.data")); From 9e9a2d19967f7290b963a04cbd64177234361bec Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 14:49:42 +0000 Subject: [PATCH 45/81] put ModuleCCSession and ModuleSpec in namespace isc::config git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@871 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/main.cc | 6 +++--- src/bin/parkinglot/main.cc | 4 +++- src/lib/config/cpp/ccsession.cc | 8 +++++--- src/lib/config/cpp/ccsession.h | 7 ++++++- src/lib/config/cpp/data_def.cc | 2 +- src/lib/config/cpp/data_def.h | 4 +++- src/lib/config/cpp/data_def_unittests.cc | 1 + 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 85e22723a7..2fe39e89f9 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -106,9 +106,9 @@ main(int argc, char* argv[]) { } else { specfile = std::string(AUTH_SPECFILE_LOCATION); } - ModuleCCSession cs = ModuleCCSession(specfile, - my_config_handler, - my_command_handler); + isc::config::ModuleCCSession cs = isc::config::ModuleCCSession(specfile, + my_config_handler, + my_command_handler); // main server loop fd_set fds; diff --git a/src/bin/parkinglot/main.cc b/src/bin/parkinglot/main.cc index 2b5086200c..ff0b38f404 100644 --- a/src/bin/parkinglot/main.cc +++ b/src/bin/parkinglot/main.cc @@ -110,7 +110,9 @@ main(int argc, char* argv[]) { } else { specfile = std::string(PARKINGLOT_SPECFILE_LOCATION); } - ModuleCCSession cs = ModuleCCSession(specfile, my_config_handler, my_command_handler); + isc::config::ModuleCCSession cs = isc::config::ModuleCCSession(specfile, + my_config_handler, + my_command_handler); // main server loop fd_set fds; diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 5249f40e0c..ff6d3f4218 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -45,10 +45,10 @@ using namespace std; using isc::data::Element; using isc::data::ElementPtr; -using isc::data::ModuleSpec; using isc::data::ParseError; -using isc::data::ModuleSpecError; -using namespace isc::data; + +namespace isc { +namespace config { void ModuleCCSession::read_module_specification(const std::string& filename) { @@ -156,3 +156,5 @@ ModuleCCSession::check_command() return 0; } +} +} diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index 53fc10460d..41a269937b 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -23,6 +23,9 @@ #include #include +namespace isc { +namespace config { + class ModuleCCSession { public: /** @@ -75,13 +78,15 @@ private: std::string module_name_; isc::cc::Session session_; - isc::data::ModuleSpec module_specification_; + ModuleSpec module_specification_; isc::data::ElementPtr config_; isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config); isc::data::ElementPtr(*command_handler_)(isc::data::ElementPtr command); }; +} +} #endif // __CCSESSION_H // Local Variables: diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/data_def.cc index c7eb3efdda..6dc6d27950 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/data_def.cc @@ -25,7 +25,7 @@ // todo: add more context to thrown ModuleSpecErrors? namespace isc { -namespace data { +namespace config { // // static functions diff --git a/src/lib/config/cpp/data_def.h b/src/lib/config/cpp/data_def.h index 5f76d97f84..30be4f2f4e 100644 --- a/src/lib/config/cpp/data_def.h +++ b/src/lib/config/cpp/data_def.h @@ -20,7 +20,9 @@ #include -namespace isc { namespace data { +using namespace isc::data; + +namespace isc { namespace config { /// /// A standard ModuleSpec exception that is thrown when a diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/data_def_unittests.cc index 6f5b138c21..6824f6d6db 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/data_def_unittests.cc @@ -23,6 +23,7 @@ #include "data_def_unittests_config.h" using namespace isc::data; +using namespace isc::config; std::string specfile(const std::string name) { return std::string(TEST_DATA_PATH) + "/" + name; From a532a314683533a2c85ee726b936801b9e1cd2d8 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 18 Feb 2010 14:57:22 +0000 Subject: [PATCH 46/81] renamed files names data_def* to module_spec* to reflect their actual content git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@872 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/Makefile.am | 4 +- src/lib/config/cpp/ccsession.cc | 2 +- src/lib/config/cpp/ccsession.h | 2 +- .../cpp/{data_def.cc => module_spec.cc} | 2 +- .../config/cpp/{data_def.h => module_spec.h} | 0 ..._unittests.cc => module_spec_unittests.cc} | 46 +++++++++---------- 6 files changed, 28 insertions(+), 28 deletions(-) rename src/lib/config/cpp/{data_def.cc => module_spec.cc} (99%) rename src/lib/config/cpp/{data_def.h => module_spec.h} (100%) rename src/lib/config/cpp/{data_def_unittests.cc => module_spec_unittests.cc} (85%) diff --git a/src/lib/config/cpp/Makefile.am b/src/lib/config/cpp/Makefile.am index 572fb9fac4..1665157011 100644 --- a/src/lib/config/cpp/Makefile.am +++ b/src/lib/config/cpp/Makefile.am @@ -1,12 +1,12 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror lib_LIBRARIES = libcfgclient.a -libcfgclient_a_SOURCES = data_def.h data_def.cc ccsession.cc ccsession.h +libcfgclient_a_SOURCES = module_spec.h module_spec.cc ccsession.cc ccsession.h TESTS = if HAVE_GTEST TESTS += run_unittests -run_unittests_SOURCES = data_def_unittests.cc run_unittests.cc +run_unittests_SOURCES = module_spec_unittests.cc run_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDADD = libcfgclient.a $(GTEST_LDADD) diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index ff6d3f4218..3f1c3d7b01 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -34,7 +34,7 @@ #include #include -#include +#include #include //#include "common.h" diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index 41a269937b..59f1117f56 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -19,7 +19,7 @@ #include -#include +#include #include #include diff --git a/src/lib/config/cpp/data_def.cc b/src/lib/config/cpp/module_spec.cc similarity index 99% rename from src/lib/config/cpp/data_def.cc rename to src/lib/config/cpp/module_spec.cc index 6dc6d27950..4c144c2eb5 100644 --- a/src/lib/config/cpp/data_def.cc +++ b/src/lib/config/cpp/module_spec.cc @@ -13,7 +13,7 @@ // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION // WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#include "data_def.h" +#include "module_spec.h" #include #include diff --git a/src/lib/config/cpp/data_def.h b/src/lib/config/cpp/module_spec.h similarity index 100% rename from src/lib/config/cpp/data_def.h rename to src/lib/config/cpp/module_spec.h diff --git a/src/lib/config/cpp/data_def_unittests.cc b/src/lib/config/cpp/module_spec_unittests.cc similarity index 85% rename from src/lib/config/cpp/data_def_unittests.cc rename to src/lib/config/cpp/module_spec_unittests.cc index 6824f6d6db..0fc8a5e037 100644 --- a/src/lib/config/cpp/data_def_unittests.cc +++ b/src/lib/config/cpp/module_spec_unittests.cc @@ -16,7 +16,7 @@ #include -#include +#include #include @@ -30,7 +30,7 @@ std::string specfile(const std::string name) { } void -data_def_error(const std::string& file, +module_spec_error(const std::string& file, const std::string& error1, const std::string& error2 = "", const std::string& error3 = "") @@ -54,7 +54,7 @@ TEST(ModuleSpec, ReadingSpecfiles) { dd = moduleSpecFromFile(specfile("spec2.spec")); EXPECT_EQ(dd.getFullSpec()->get("module_spec") ->get("config_data")->size(), 6); - data_def_error("doesnotexist", + module_spec_error("doesnotexist", "Error opening ", specfile("doesnotexist"), ": No such file or directory"); @@ -68,54 +68,54 @@ TEST(ModuleSpec, ReadingSpecfiles) { } TEST(ModuleSpec, SpecfileItems) { - data_def_error("spec3.spec", + module_spec_error("spec3.spec", "item_name missing in {\"item_default\": 1, \"item_optional\": False, \"item_type\": \"integer\"}"); - data_def_error("spec4.spec", + module_spec_error("spec4.spec", "item_type missing in {\"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": False}"); - data_def_error("spec5.spec", + module_spec_error("spec5.spec", "item_optional missing in {\"item_default\": 1, \"item_name\": \"item1\", \"item_type\": \"integer\"}"); - data_def_error("spec6.spec", + module_spec_error("spec6.spec", "item_default missing in {\"item_name\": \"item1\", \"item_optional\": False, \"item_type\": \"integer\"}"); - data_def_error("spec9.spec", + module_spec_error("spec9.spec", "item_default not of type integer"); - data_def_error("spec10.spec", + module_spec_error("spec10.spec", "item_default not of type real"); - data_def_error("spec11.spec", + module_spec_error("spec11.spec", "item_default not of type boolean"); - data_def_error("spec12.spec", + module_spec_error("spec12.spec", "item_default not of type string"); - data_def_error("spec13.spec", + module_spec_error("spec13.spec", "item_default not of type list"); - data_def_error("spec14.spec", + module_spec_error("spec14.spec", "item_default not of type map"); - data_def_error("spec15.spec", + module_spec_error("spec15.spec", "badname is not a valid type name"); } TEST(ModuleSpec, SpecfileConfigData) { - data_def_error("spec7.spec", + module_spec_error("spec7.spec", "module_name missing in {}"); - data_def_error("spec8.spec", + module_spec_error("spec8.spec", "Data specification does not contain module_spec element"); - data_def_error("spec16.spec", + module_spec_error("spec16.spec", "config_data is not a list of elements"); - data_def_error("spec21.spec", + module_spec_error("spec21.spec", "commands is not a list of elements"); } TEST(ModuleSpec, SpecfileCommands) { - data_def_error("spec17.spec", + module_spec_error("spec17.spec", "command_name missing in {\"command_args\": [ {\"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": False, \"item_type\": \"string\"} ], \"command_description\": \"Print the given message to stdout\"}"); - data_def_error("spec18.spec", + module_spec_error("spec18.spec", "command_args missing in {\"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\"}"); - data_def_error("spec19.spec", + module_spec_error("spec19.spec", "command_args not of type list"); - data_def_error("spec20.spec", + module_spec_error("spec20.spec", "somethingbad is not a valid type name"); /* - data_def_error("spec22.spec", + module_spec_error("spec22.spec", ""); */ } From ffd4fcba407df2c83b79cc9cdee334e0527bcedf Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 18 Feb 2010 18:44:05 +0000 Subject: [PATCH 47/81] added more unittests for RRset git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@873 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rrset_unittest.cc | 137 ++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 16 deletions(-) diff --git a/src/lib/dns/cpp/rrset_unittest.cc b/src/lib/dns/cpp/rrset_unittest.cc index dfee0e88fc..9de29385ab 100644 --- a/src/lib/dns/cpp/rrset_unittest.cc +++ b/src/lib/dns/cpp/rrset_unittest.cc @@ -14,6 +14,8 @@ // $Id$ +#include + #include "buffer.h" #include "messagerenderer.h" #include "name.h" @@ -42,9 +44,16 @@ protected: test_domain("example.com"), test_nsname("ns.example.com"), rrset_a(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)), + rrset_a_empty(test_name, RRClass::IN(), RRType::A(), + RRTTL(3600)), rrset_ns(test_domain, RRClass::IN(), RRType::NS(), - RRTTL(86400)) - {} + RRTTL(86400)), + rrset_ch_txt(test_domain, RRClass::CH(), RRType::TXT(), + RRTTL(0)) + { + rrset_a.addRdata(in::A("192.0.2.1")); + rrset_a.addRdata(in::A("192.0.2.2")); + } OutputBuffer buffer; MessageRenderer renderer; @@ -52,33 +61,124 @@ protected: Name test_domain; Name test_nsname; RRset rrset_a; + RRset rrset_a_empty; RRset rrset_ns; + RRset rrset_ch_txt; std::vector wiredata; + + // max number of Rdata objects added to a test RRset object. + // this is an arbitrary chosen limit, but should be sufficiently large + // in practice and reasonable even as an extreme test case. + static const int MAX_RDATA_COUNT = 100; }; +TEST_F(RRsetTest, getRdataCount) +{ + for (int i = 0; i < MAX_RDATA_COUNT; ++i) { + EXPECT_EQ(i, rrset_a_empty.getRdataCount()); + rrset_a_empty.addRdata(in::A("192.0.2.1")); + } +} + +TEST_F(RRsetTest, getName) +{ + EXPECT_EQ(test_name, rrset_a.getName()); + EXPECT_EQ(test_domain, rrset_ns.getName()); +} + +TEST_F(RRsetTest, getClass) +{ + EXPECT_EQ(RRClass("IN"), rrset_a.getClass()); + EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass()); +} + +TEST_F(RRsetTest, getType) +{ + EXPECT_EQ(RRType("A"), rrset_a.getType()); + EXPECT_EQ(RRType("NS"), rrset_ns.getType()); + EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType()); +} + +TEST_F(RRsetTest, getTTL) +{ + EXPECT_EQ(RRTTL(3600), rrset_a.getTTL()); + EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL()); + EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL()); +} + +TEST_F(RRsetTest, setTTL) +{ + rrset_a.setTTL(RRTTL(86400)); + EXPECT_EQ(RRTTL(86400), rrset_a.getTTL()); + rrset_a.setTTL(RRTTL(0)); + EXPECT_EQ(RRTTL(0), rrset_a.getTTL()); +} + +void +addRdataTestCommon(const RRset& rrset) +{ + EXPECT_EQ(2, rrset.getRdataCount()); + + RdataIteratorPtr it = rrset.getRdataIterator(); + it->first(); + EXPECT_FALSE(it->isLast()); + EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1"))); + it->next(); + EXPECT_FALSE(it->isLast()); + EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2"))); + it->next(); + EXPECT_TRUE(it->isLast()); +} + TEST_F(RRsetTest, addRdata) { - rrset_a.addRdata(in::A("192.0.2.1")); - rrset_a.addRdata(in::A("192.0.2.2")); + addRdataTestCommon(rrset_a); - RdataIteratorPtr it = rrset_a.getRdataIterator(); + // Reference version of addRdata() doesn't allow to add a different + // type of Rdata. + EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), std::bad_cast); +} + +TEST_F(RRsetTest, addRdataPtr) +{ + rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(), + rrset_a_empty.getClass(), + "192.0.2.1")); + rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(), + rrset_a_empty.getClass(), + "192.0.2.2")); + + addRdataTestCommon(rrset_a); + + // Pointer version of addRdata() doesn't type check and does allow to + //add a different type of Rdata as a result. + rrset_a_empty.addRdata(createRdata(RRType::NS(), RRClass::IN(), + "ns.example.com")); + EXPECT_EQ(3, rrset_a_empty.getRdataCount()); +} + +TEST_F(RRsetTest, iterator) +{ + // Iterator for an empty RRset. + RdataIteratorPtr it = rrset_a_empty.getRdataIterator(); it->first(); - EXPECT_FALSE(it->isLast()); - EXPECT_EQ("192.0.2.1", it->getCurrent().toText()); - it->next(); - EXPECT_FALSE(it->isLast()); - EXPECT_EQ("192.0.2.2", it->getCurrent().toText()); - it->next(); EXPECT_TRUE(it->isLast()); + // Normal case (already tested, but do it again just in case) + rrset_a_empty.addRdata(in::A("192.0.2.1")); + rrset_a_empty.addRdata(in::A("192.0.2.2")); + addRdataTestCommon(rrset_a_empty); +} + +TEST_F(RRsetTest, toText) +{ EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n" - "test.example.com. 3600 IN A 192.0.2.2\n", rrset_a.toText()); + "test.example.com. 3600 IN A 192.0.2.2\n", + rrset_a.toText()); } TEST_F(RRsetTest, toWireBuffer) { - rrset_a.addRdata(in::A("192.0.2.1")); - rrset_a.addRdata(in::A("192.0.2.2")); rrset_a.toWire(buffer); UnitTestUtil::readWireData("testdata/rrset_toWire1", wiredata); @@ -88,8 +188,6 @@ TEST_F(RRsetTest, toWireBuffer) TEST_F(RRsetTest, toWireRenderer) { - rrset_a.addRdata(in::A("192.0.2.1")); - rrset_a.addRdata(in::A("192.0.2.2")); rrset_ns.addRdata(generic::NS(test_nsname)); rrset_a.toWire(renderer); @@ -100,4 +198,11 @@ TEST_F(RRsetTest, toWireRenderer) buffer.getLength(), &wiredata[0], wiredata.size()); } +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(RRsetTest, LeftShiftOperator) +{ + ostringstream oss; + oss << rrset_a; + EXPECT_EQ(rrset_a.toText(), oss.str()); +} } From f3a53f5572130cb4e0a15a62e29121d256b31cde Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 19 Feb 2010 09:29:42 +0000 Subject: [PATCH 48/81] didn't update one function git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@879 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 3142ee6c37..955ada75c2 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -233,19 +233,19 @@ class UIModuleCCSession(MultiConfigData): a DataTypeError if the value at the identifier is not a list, or if the given value_str does not match the list_item_spec """ - module_spec = find_spec(self.config.specification, identifier) + module_spec = self.find_spec_part(identifier) if (type(module_spec) != dict or "list_item_spec" not in module_spec): raise DataTypeError(identifier + " is not a list") - value = parse_value_str(value_str) - check_type(module_spec, [value]) - cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier) - if not cur_list: - cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) + value = isc.cc.data.parse_value_str(value_str) + isc.config.config_data.check_type(module_spec, [value]) + cur_list, status = self.get_value(identifier) + #if not cur_list: + # cur_list = isc.cc.data.find_no_exc(self.config.data, identifier) if not cur_list: cur_list = [] if value in cur_list: cur_list.remove(value) - set(self.config_changes, identifier, cur_list) + self.set_value(identifier, cur_list) def commit(self): """Commit all local changes, send them through b10-cmdctl to From da8e6967bd03ba7d779f96429810129803b51594 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 19 Feb 2010 10:28:24 +0000 Subject: [PATCH 49/81] moduleccsession now validates config updates before calling the provided config_handler renamed validate() to validate_config() (plan on adding validate_command() as well) added createAnswer function in cpp version git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@880 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10.py.in | 10 +----- src/lib/config/cpp/ccsession.cc | 31 ++++++++++++++++--- src/lib/config/cpp/ccsession.h | 3 ++ src/lib/config/cpp/module_spec.cc | 2 +- src/lib/config/cpp/module_spec.h | 2 +- src/lib/config/cpp/module_spec_unittests.cc | 2 +- src/lib/config/python/isc/config/ccsession.py | 13 ++++++-- .../config/python/isc/config/config_data.py | 3 +- .../config/python/isc/config/module_spec.py | 2 +- .../python/isc/config/module_spec_test.py | 2 +- 10 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 61e3b90892..b79f766d2a 100644 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -115,15 +115,7 @@ class BoB: if self.verbose: print("[XX] handling new config:") print(new_config) - errors = [] - if self.ccs.get_module_spec().validate(False, new_config, errors): - self.ccs.set_local_config(new_config) - answer = isc.config.ccsession.create_answer(0) - else: - if len(errors) > 0: - answer = isc.config.ccsession.create_answer(1, " ".join(errors)) - else: - answer = isc.config.ccsession.create_answer(1, "Unknown error in validation") + answer = isc.config.ccsession.create_answer(0) return answer # TODO diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 3f1c3d7b01..2531900ef5 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -50,6 +50,26 @@ using isc::data::ParseError; namespace isc { namespace config { +ElementPtr +create_answer(const int rcode, const ElementPtr arg) +{ + ElementPtr answer = Element::createFromString("{\"result\": []"); + ElementPtr answer_content = answer->get("result"); + answer_content->add(Element::create(rcode)); + answer_content->add(arg); + return answer; +} + +ElementPtr +create_answer(const int rcode, const std::string& arg) +{ + ElementPtr answer = Element::createFromString("{\"result\": []"); + ElementPtr answer_content = answer->get("result"); + answer_content->add(Element::create(rcode)); + answer_content->add(Element::create(arg)); + return answer; +} + void ModuleCCSession::read_module_specification(const std::string& filename) { std::ifstream file; @@ -136,11 +156,14 @@ ModuleCCSession::check_command() cout << "[XX] got something!" << endl << data->str() << endl; ElementPtr answer; if (data->contains("config_update")) { - if (config_handler_) { - // handle config update - answer = config_handler_(data->get("config_update")); + ElementPtr new_config = data->get("config_update"); + if (!config_handler_) { + answer = create_answer(1, module_name_ + " does not have a config handler"); + } else if (!module_specification_.validate_config(new_config)) { + answer = create_answer(2, "Error in config validation"); } else { - answer = Element::createFromString("{ \"result\": [0] }"); + // handle config update + answer = config_handler_(new_config); } } if (data->contains("command")) { diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index 59f1117f56..d7340c90d0 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -85,6 +85,9 @@ private: isc::data::ElementPtr(*command_handler_)(isc::data::ElementPtr command); }; +ElementPtr createAnswer(const int rcode, const ElementPtr arg); +ElementPtr createAnswer(const int rcode, const std::string arg); + } } #endif // __CCSESSION_H diff --git a/src/lib/config/cpp/module_spec.cc b/src/lib/config/cpp/module_spec.cc index 4c144c2eb5..154008b3bd 100644 --- a/src/lib/config/cpp/module_spec.cc +++ b/src/lib/config/cpp/module_spec.cc @@ -209,7 +209,7 @@ ModuleSpec::getModuleName() } bool -ModuleSpec::validate(const ElementPtr data) +ModuleSpec::validate_config(const ElementPtr data) { ElementPtr spec = module_specification->find("module_spec/config_data"); return validate_spec_list(spec, data); diff --git a/src/lib/config/cpp/module_spec.h b/src/lib/config/cpp/module_spec.h index 30be4f2f4e..af96a30c69 100644 --- a/src/lib/config/cpp/module_spec.h +++ b/src/lib/config/cpp/module_spec.h @@ -83,7 +83,7 @@ namespace isc { namespace config { /// \param data The base \c Element of the data to check /// \return true if the data conforms to the specification, /// false otherwise. - bool validate(const ElementPtr data); + bool validate_config(const ElementPtr data); private: bool validate_item(const ElementPtr spec, const ElementPtr data); diff --git a/src/lib/config/cpp/module_spec_unittests.cc b/src/lib/config/cpp/module_spec_unittests.cc index 0fc8a5e037..29ee4fc8ee 100644 --- a/src/lib/config/cpp/module_spec_unittests.cc +++ b/src/lib/config/cpp/module_spec_unittests.cc @@ -129,7 +129,7 @@ data_test(ModuleSpec dd, const std::string& data_file_name) ElementPtr data = Element::createFromString(data_file, data_file_name); data_file.close(); - return dd.validate(data); + return dd.validate_config(data); } TEST(ModuleSpec, DataValidation) { diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 955ada75c2..25a9d79222 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -135,8 +135,15 @@ class ModuleCCSession(ConfigData): if msg: answer = None try: - if "config_update" in msg and self._config_handler: - answer = self._config_handler(msg["config_update"]) + if "config_update" in msg: + new_config = msg["config_update"] + errors = [] + if not self._config_handler: + answer = create_answer(2, self._module_name + " has no config handler") + elif not self.get_module_spec().validate_config(False, new_config, errors): + answer = create_answer(1, " ".join(errors)) + else: + answer = self._config_handler(msg["config_update"]) if "command" in msg and self._command_handler: answer = self._command_handler(msg["command"]) except Exception as exc: @@ -168,7 +175,7 @@ class ModuleCCSession(ConfigData): answer, env = self._session.group_recvmsg(False) rcode, value = parse_answer(answer) if rcode == 0: - if value != None and self.get_module_spec().validate(False, value): + if value != None and self.get_module_spec().validate_config(False, value): self.set_local_config(value); if self._config_handler: self._config_handler(value) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 9ce1b80821..ccc281e021 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -27,7 +27,8 @@ class ConfigDataError(Exception): pass def check_type(spec_part, value): """Returns true if the value is of the correct type given the - specification part relevant for the value""" + specification part relevant for the value. spec_part can be + retrieved with find_spec()""" if type(spec_part) == list: data_type = "list" else: diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index 56cfb9ccb7..e9b2cf1a7a 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -64,7 +64,7 @@ class ModuleSpec: _check(module_spec) self._module_spec = module_spec - def validate(self, full, data, errors = None): + def validate_config(self, full, data, errors = None): """Check whether the given piece of data conforms to this data definition. If so, it returns True. If not, it will return false. If errors is given, and is an array, a string diff --git a/src/lib/config/python/isc/config/module_spec_test.py b/src/lib/config/python/isc/config/module_spec_test.py index 9ccaa310ac..1b4a948eb6 100644 --- a/src/lib/config/python/isc/config/module_spec_test.py +++ b/src/lib/config/python/isc/config/module_spec_test.py @@ -76,7 +76,7 @@ class TestModuleSpec(unittest.TestCase): data_file = open(self.spec_file(datafile_name)) data_str = data_file.read() data = isc.cc.data.parse_value_str(data_str) - return dd.validate(True, data) + return dd.validate_config(True, data) def test_data_validation(self): self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data")) From 33f2f0f5f34ffcd06898c55b7fb1c17c72600dd7 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 19 Feb 2010 12:54:14 +0000 Subject: [PATCH 50/81] add parseAnswer helper function moved two sets of common code into one private handleConfigUpdate function git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@881 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth_srv.cc | 4 +- src/bin/auth/main.cc | 5 +-- src/lib/cc/cpp/data.h | 1 - src/lib/config/cpp/ccsession.cc | 68 +++++++++++++++++++++++++-------- src/lib/config/cpp/ccsession.h | 17 ++++++++- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc index 7045f81639..d8bd2db25d 100644 --- a/src/bin/auth/auth_srv.cc +++ b/src/bin/auth/auth_srv.cc @@ -120,5 +120,7 @@ AuthSrv::updateConfig(isc::data::ElementPtr config) { // todo: what to do with port change. restart automatically? // ignore atm //} - return isc::data::Element::createFromString("{ \"result\": [0] }"); + std::cout << "[XX] auth: new config " << config << std::endl; + + return isc::config::createAnswer(0); } diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 2fe39e89f9..df050a5a6f 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -64,18 +64,15 @@ my_config_handler(isc::data::ElementPtr config) isc::data::ElementPtr my_command_handler(isc::data::ElementPtr command) { - isc::data::ElementPtr answer = isc::data::Element::createFromString("{ \"result\": [0] }"); + isc::data::ElementPtr answer = isc::config::createAnswer(0); cout << "[XX] Handle command: " << endl << command->str() << endl; if (command->get(0)->stringValue() == "print_message") { cout << command->get(1)->get("message") << endl; /* let's add that message to our answer as well */ - cout << "[XX] answer was: " << answer->str() << endl; answer->get("result")->add(command->get(1)); - cout << "[XX] answer now: " << answer->str() << endl; } - return answer; } diff --git a/src/lib/cc/cpp/data.h b/src/lib/cc/cpp/data.h index 76ac36f196..ad1fa49bab 100644 --- a/src/lib/cc/cpp/data.h +++ b/src/lib/cc/cpp/data.h @@ -35,7 +35,6 @@ typedef boost::shared_ptr ElementPtr; /// is called for an Element that has a wrong type (e.g. int_value on a /// ListElement) /// -// todo: include types and called function in the exception class TypeError : public isc::Exception { public: TypeError(const char* file, size_t line, const char* what) : diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 2531900ef5..ae6df1b525 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -36,6 +36,7 @@ #include #include #include +#include //#include "common.h" #include "ccsession.h" @@ -50,8 +51,9 @@ using isc::data::ParseError; namespace isc { namespace config { +/// Creates a standard config/command protocol answer message ElementPtr -create_answer(const int rcode, const ElementPtr arg) +createAnswer(const int rcode, const ElementPtr arg) { ElementPtr answer = Element::createFromString("{\"result\": []"); ElementPtr answer_content = answer->get("result"); @@ -61,7 +63,7 @@ create_answer(const int rcode, const ElementPtr arg) } ElementPtr -create_answer(const int rcode, const std::string& arg) +createAnswer(const int rcode, const std::string& arg) { ElementPtr answer = Element::createFromString("{\"result\": []"); ElementPtr answer_content = answer->get("result"); @@ -70,6 +72,28 @@ create_answer(const int rcode, const std::string& arg) return answer; } +ElementPtr +parseAnswer(int &rcode, const ElementPtr msg) +{ + if (!msg->contains("result")) { + // TODO: raise CCSessionError exception + dns_throw(CCSessionError, "No result in answer message"); + } else { + ElementPtr result = msg->get("result"); + if (result->get(0)->getType() != Element::integer) { + dns_throw(CCSessionError, "First element of result is not an rcode in answer message"); + } else if (result->get(0)->intValue() != 0 && result->get(1)->getType() != Element::string) { + dns_throw(CCSessionError, "Rcode in answer message is non-zero, but other argument is not a StringElement"); + } + rcode = result->get(0)->intValue(); + if (result->size() > 1) { + return result->get(1); + } else { + return ElementPtr(); + } + } +} + void ModuleCCSession::read_module_specification(const std::string& filename) { std::ifstream file; @@ -126,14 +150,33 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name, session_.group_sendmsg(cmd, "ConfigManager"); session_.group_recvmsg(env, answer, false); cout << "[XX] got config: " << endl << answer->str() << endl; - if (answer->contains("result") && - answer->get("result")->get(0)->intValue() == 0 && - answer->get("result")->size() > 1) { - config_handler(answer->get("result")->get(1)); - } else { - cout << "[XX] no result in answer" << endl; + int rcode; + ElementPtr new_config = parseAnswer(rcode, answer); + handleConfigUpdate(new_config); + } +} + +/// Validates the new config values, if they are correct, +/// call the config handler +/// If that results in success, store the new config +ElementPtr +ModuleCCSession::handleConfigUpdate(ElementPtr new_config) +{ + ElementPtr answer; + if (!config_handler_) { + answer = createAnswer(1, module_name_ + " does not have a config handler"); + } else if (!module_specification_.validate_config(new_config)) { + answer = createAnswer(2, "Error in config validation"); + } else { + // handle config update + answer = config_handler_(new_config); + int rcode; + parseAnswer(rcode, answer); + if (rcode == 0) { + config_ = new_config; } } + return answer; } int @@ -157,14 +200,7 @@ ModuleCCSession::check_command() ElementPtr answer; if (data->contains("config_update")) { ElementPtr new_config = data->get("config_update"); - if (!config_handler_) { - answer = create_answer(1, module_name_ + " does not have a config handler"); - } else if (!module_specification_.validate_config(new_config)) { - answer = create_answer(2, "Error in config validation"); - } else { - // handle config update - answer = config_handler_(new_config); - } + answer = handleConfigUpdate(new_config); } if (data->contains("command")) { if (command_handler_) { diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index d7340c90d0..c48cae34ee 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -26,6 +26,18 @@ namespace isc { namespace config { +/// +/// \brief A standard cc session exception that is thrown if a function +/// is there is a problem with one of the messages +/// +// todo: include types and called function in the exception +class CCSessionError : public isc::Exception { +public: + CCSessionError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + + class ModuleCCSession { public: /** @@ -72,7 +84,7 @@ public: * This protocol is very likely to change. */ void set_command_handler(isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)) { command_handler_ = command_handler; }; - + private: void read_module_specification(const std::string& filename); @@ -80,13 +92,14 @@ private: isc::cc::Session session_; ModuleSpec module_specification_; isc::data::ElementPtr config_; + ElementPtr handleConfigUpdate(ElementPtr new_config); isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config); isc::data::ElementPtr(*command_handler_)(isc::data::ElementPtr command); }; ElementPtr createAnswer(const int rcode, const ElementPtr arg); -ElementPtr createAnswer(const int rcode, const std::string arg); +ElementPtr createAnswer(const int rcode, const std::string& arg); } } From 3a69c8a7a8baf4d8800039150da3555650571d01 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 22 Feb 2010 12:42:15 +0000 Subject: [PATCH 51/81] added error feedback to config_validate() and option to validate partial configurations in cpp version fixed a bug in Mapelement->str() (it can now print empty map elements) added tests for python config_data module and fixed a few bugs there git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@911 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/auth/auth_srv.cc | 6 +- src/lib/cc/cpp/data.cc | 6 +- src/lib/cc/cpp/data.h | 2 +- src/lib/config/cpp/ccsession.cc | 13 +- src/lib/config/cpp/module_spec.cc | 38 ++- src/lib/config/cpp/module_spec.h | 11 +- src/lib/config/python/isc/config/cfgmgr.py | 7 +- .../config/python/isc/config/config_data.py | 92 ++++--- .../python/isc/config/config_data_test.py | 260 +++++++++++++++++- 9 files changed, 375 insertions(+), 60 deletions(-) diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc index 75eb5bcd15..27f67c0f4b 100644 --- a/src/bin/auth/auth_srv.cc +++ b/src/bin/auth/auth_srv.cc @@ -122,7 +122,11 @@ AuthSrv::updateConfig(isc::data::ElementPtr config) { // todo: what to do with port change. restart automatically? // ignore atm //} - std::cout << "[XX] auth: new config " << config << std::endl; + if (config) { + std::cout << "[XX] auth: new config " << config << std::endl; + } else { + std::cout << "[XX] auth: new config empty" << std::endl; + } return isc::config::createAnswer(0); } diff --git a/src/lib/cc/cpp/data.cc b/src/lib/cc/cpp/data.cc index ee4827b799..b75eba11c3 100644 --- a/src/lib/cc/cpp/data.cc +++ b/src/lib/cc/cpp/data.cc @@ -487,7 +487,11 @@ MapElement::str() ss << ", "; } ss << "\"" << (*it).first << "\": "; - ss << (*it).second->str(); + if ((*it).second) { + ss << (*it).second->str(); + } else { + ss << "None"; + } } ss << "}"; return ss.str(); diff --git a/src/lib/cc/cpp/data.h b/src/lib/cc/cpp/data.h index ad1fa49bab..220b3d04fe 100644 --- a/src/lib/cc/cpp/data.h +++ b/src/lib/cc/cpp/data.h @@ -409,7 +409,7 @@ public: using Element::setValue; bool setValue(std::map& v) { m = v; return true; }; using Element::get; - ElementPtr get(const std::string& s) { return m[s]; }; + ElementPtr get(const std::string& s) { if (contains(s)) { return m[s]; } else { return ElementPtr();} }; using Element::set; void set(const std::string& s, ElementPtr p) { m[s] = p; }; using Element::remove; diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 4883ca8a90..306eb8ea6b 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -172,12 +172,20 @@ ElementPtr ModuleCCSession::handleConfigUpdate(ElementPtr new_config) { ElementPtr answer; + ElementPtr errors = Element::createFromString("[]"); + std::cout << "handleConfigUpdate " << new_config << std::endl; if (!config_handler_) { answer = createAnswer(1, module_name_ + " does not have a config handler"); - } else if (!module_specification_.validate_config(new_config)) { - answer = createAnswer(2, "Error in config validation"); + } else if (!module_specification_.validate_config(new_config, false, errors)) { + std::stringstream ss; + ss << "Error in config validation: "; + BOOST_FOREACH(ElementPtr error, errors->listValue()) { + ss << error->stringValue(); + } + answer = createAnswer(2, ss.str()); } else { // handle config update + std::cout << "handleConfigUpdate " << new_config << std::endl; answer = config_handler_(new_config); int rcode; parseAnswer(rcode, answer); @@ -185,6 +193,7 @@ ModuleCCSession::handleConfigUpdate(ElementPtr new_config) config_ = new_config; } } + std::cout << "end handleConfigUpdate " << new_config << std::endl; return answer; } diff --git a/src/lib/config/cpp/module_spec.cc b/src/lib/config/cpp/module_spec.cc index 154008b3bd..27314f38d1 100644 --- a/src/lib/config/cpp/module_spec.cc +++ b/src/lib/config/cpp/module_spec.cc @@ -209,10 +209,17 @@ ModuleSpec::getModuleName() } bool -ModuleSpec::validate_config(const ElementPtr data) +ModuleSpec::validate_config(const ElementPtr data, const bool full) { ElementPtr spec = module_specification->find("module_spec/config_data"); - return validate_spec_list(spec, data); + return validate_spec_list(spec, data, full, ElementPtr()); +} + +bool +ModuleSpec::validate_config(const ElementPtr data, const bool full, ElementPtr errors) +{ + ElementPtr spec = module_specification->find("module_spec/config_data"); + return validate_spec_list(spec, data, full, errors); } ModuleSpec @@ -279,28 +286,34 @@ check_type(ElementPtr spec, ElementPtr element) } bool -ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) { if (!check_type(spec, data)) { // we should do some proper error feedback here // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl; // std::cout << spec << std::endl; + if (errors) { + errors->add(Element::create("Type mismatch")); + } return false; } if (data->getType() == Element::list) { ElementPtr list_spec = spec->get("list_item_spec"); BOOST_FOREACH(ElementPtr list_el, data->listValue()) { if (!check_type(list_spec, list_el)) { + if (errors) { + errors->add(Element::create("Type mismatch")); + } return false; } if (list_spec->get("item_type")->stringValue() == "map") { - if (!validate_item(list_spec, list_el)) { + if (!validate_item(list_spec, list_el, full, errors)) { return false; } } } } if (data->getType() == Element::map) { - if (!validate_spec_list(spec->get("map_item_spec"), data)) { + if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) { return false; } } @@ -309,18 +322,21 @@ ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data) { // spec is a map with item_name etc, data is a map bool -ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) { std::string item_name = spec->get("item_name")->stringValue(); bool optional = spec->get("item_optional")->boolValue(); ElementPtr data_el; - data_el = data->get(item_name); + if (data_el) { - if (!validate_item(spec, data_el)) { + if (!validate_item(spec, data_el, full, errors)) { return false; } } else { - if (!optional) { + if (!optional && full) { + if (errors) { + errors->add(Element::create("Non-optional value missing")); + } return false; } } @@ -329,11 +345,11 @@ ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data) { // spec is a list of maps, data is a map bool -ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data) { +ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) { ElementPtr cur_data_el; std::string cur_item_name; BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) { - if (!validate_spec(cur_spec_el, data)) { + if (!validate_spec(cur_spec_el, data, full, errors)) { return false; } } diff --git a/src/lib/config/cpp/module_spec.h b/src/lib/config/cpp/module_spec.h index af96a30c69..97059e82fb 100644 --- a/src/lib/config/cpp/module_spec.h +++ b/src/lib/config/cpp/module_spec.h @@ -83,12 +83,15 @@ namespace isc { namespace config { /// \param data The base \c Element of the data to check /// \return true if the data conforms to the specification, /// false otherwise. - bool validate_config(const ElementPtr data); + bool validate_config(const ElementPtr data, const bool full = false); + + /// errors must be of type ListElement + bool validate_config(const ElementPtr data, const bool full, ElementPtr errors); private: - bool validate_item(const ElementPtr spec, const ElementPtr data); - bool validate_spec(const ElementPtr spec, const ElementPtr data); - bool validate_spec_list(const ElementPtr spec, const ElementPtr data); + bool validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors); + bool validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors); + bool validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors); ElementPtr module_specification; }; diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index ea9f9986bc..c4425cd5cc 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -230,6 +230,8 @@ class ConfigManager: # todo: use api (and check the data against the definition?) module_name = cmd[1] conf_part = data.find_no_exc(self.config.data, module_name) + print("[XX] cfgmgr conf part:") + print(conf_part) if conf_part: data.merge(conf_part, cmd[2]) self.cc.group_sendmsg({ "config_update": conf_part }, module_name) @@ -246,6 +248,7 @@ class ConfigManager: self.write_config() elif len(cmd) == 2: # todo: use api (and check the data against the definition?) + old_data = self.config.data.copy() data.merge(self.config.data, cmd[1]) # send out changed info got_error = False @@ -262,8 +265,8 @@ class ConfigManager: self.write_config() answer = isc.config.ccsession.create_answer(0) else: - # TODO rollback changes that did get through? - # feed back *all* errors? + # TODO rollback changes that did get through, should we re-send update? + self.config.data = old_data answer = isc.config.ccsession.create_answer(1, " ".join(err_list)) else: answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index ccc281e021..9270f499d1 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -26,9 +26,10 @@ import isc.config.module_spec class ConfigDataError(Exception): pass def check_type(spec_part, value): - """Returns true if the value is of the correct type given the - specification part relevant for the value. spec_part can be - retrieved with find_spec()""" + """Does nothing if the value is of the correct type given the + specification part relevant for the value. Raises an + isc.cc.data.DataTypeError exception if not. spec_part can be + retrieved with find_spec_part()""" if type(spec_part) == list: data_type = "list" else: @@ -36,9 +37,9 @@ def check_type(spec_part, value): if data_type == "integer" and type(value) != int: raise isc.cc.data.DataTypeError(str(value) + " is not an integer") - elif data_type == "real" and type(value) != double: + elif data_type == "real" and type(value) != float: raise isc.cc.data.DataTypeError(str(value) + " is not a real") - elif data_type == "boolean" and type(value) != boolean: + elif data_type == "boolean" and type(value) != bool: raise isc.cc.data.DataTypeError(str(value) + " is not a boolean") elif data_type == "string" and type(value) != str: raise isc.cc.data.DataTypeError(str(value) + " is not a string") @@ -52,7 +53,7 @@ def check_type(spec_part, value): # todo: check types of map contents too raise isc.cc.data.DataTypeError(str(value) + " is not a map") -def find_spec(element, identifier): +def find_spec_part(element, identifier): """find the data definition for the given identifier returns either a map with 'item_name' etc, or a list of those""" if identifier == "": @@ -65,6 +66,14 @@ def find_spec(element, identifier): cur_el = cur_el[id] elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id: pass + elif type(cur_el) == dict and 'map_item_spec' in cur_el.keys(): + found = False + for cur_el_item in cur_el['map_item_spec']: + if cur_el_item['item_name'] == id: + cur_el = cur_el_item + found = True + if not found: + raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el)) elif type(cur_el) == list: found = False for cur_el_item in cur_el: @@ -84,24 +93,30 @@ def spec_name_list(spec, prefix="", recurse=False): if prefix != "" and not prefix.endswith("/"): prefix += "/" if type(spec) == dict: - for name in spec: - result.append(prefix + name + "/") - if recurse: - result.extend(spec_name_list(spec[name],name, recurse)) + if 'map_item_spec' in spec: + for map_el in spec['map_item_spec']: + name = map_el['item_name'] + if map_el['item_type'] == 'map': + name += "/" + result.append(prefix + name) + else: + for name in spec: + result.append(prefix + name + "/") + if recurse: + print("[XX] recurse1") + result.extend(spec_name_list(spec[name],name, recurse)) elif type(spec) == list: for list_el in spec: if 'item_name' in list_el: - if list_el['item_type'] == dict: - if recurse: - result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse)) + if list_el['item_type'] == "map" and recurse: + result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse)) else: name = list_el['item_name'] if list_el['item_type'] in ["list", "map"]: name += "/" - result.append(name) + result.append(prefix + name) return result - class ConfigData: """This class stores the module specs and the current non-default config values. It provides functions to get the actual value or @@ -118,11 +133,12 @@ class ConfigData: def get_value(self, identifier): """Returns a tuple where the first item is the value at the given identifier, and the second item is a bool which is - true if the value is an unset default""" + true if the value is an unset default. Raises an + isc.cc.data.DataNotFoundError if the identifier is bad""" value = isc.cc.data.find_no_exc(self.data, identifier) - if value: + if value != None: return value, False - spec = find_spec(self.specification.get_config_spec(), identifier) + spec = find_spec_part(self.specification.get_config_spec(), identifier) if spec and 'item_default' in spec: return spec['item_default'], True return None, False @@ -144,7 +160,7 @@ class ConfigData: all 'sub'options at the given identifier. If recurse is True, it will also add all identifiers of all children, if any""" if identifier: - spec = find_spec(self.specification.get_config_spec(), identifier, recurse) + spec = find_spec_part(self.specification.get_config_spec(), identifier) return spec_name_list(spec, identifier + "/") return spec_name_list(self.specification.get_config_spec(), "", recurse) @@ -183,7 +199,8 @@ class MultiConfigData: self._specifications[spec.get_module_name()] = spec def get_module_spec(self, module): - """Returns the ModuleSpec for the module with the given name""" + """Returns the ModuleSpec for the module with the given name. + If there is no such module, it returns None""" if module in self._specifications: return self._specifications[module] else: @@ -193,14 +210,16 @@ class MultiConfigData: """Returns the specification for the item at the given identifier, or None if not found. The first part of the identifier (up to the first /) is interpreted as the module - name.""" + name. Returns None if not found.""" if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition("/") try: - return find_spec(self._specifications[module].get_config_spec(), id) + return find_spec_part(self._specifications[module].get_config_spec(), id) except isc.cc.data.DataNotFoundError as dnfe: return None + except KeyError as ke: + return None # this function should only be called by __request_config def _set_current_config(self, config): @@ -252,7 +271,7 @@ class MultiConfigData: identifier = identifier[1:] module, sep, id = identifier.partition("/") try: - spec = find_spec(self._specifications[module].get_config_spec(), id) + spec = find_spec_part(self._specifications[module].get_config_spec(), id) if 'item_default' in spec: return spec['item_default'] else: @@ -268,13 +287,13 @@ class MultiConfigData: (local change, current setting, default as specified by the specification, or not found at all).""" value = self.get_local_value(identifier) - if value: + if value != None: return value, self.LOCAL value = self.get_current_value(identifier) - if value: + if value != None: return value, self.CURRENT value = self.get_default_value(identifier) - if value: + if value != None: return value, self.DEFAULT return None, self.NONE @@ -305,7 +324,7 @@ class MultiConfigData: module, sep, id = identifier.partition('/') spec = self.get_module_spec(module) if spec: - spec_part = find_spec(spec.get_config_spec(), id) + spec_part = find_spec_part(spec.get_config_spec(), id) print(spec_part) if type(spec_part) == list: for item in spec_part: @@ -357,12 +376,15 @@ class MultiConfigData: return result def set_value(self, identifier, value): - """Set the local value at the given identifier to value""" + """Set the local value at the given identifier to value. If + there is a specification for the given identifier, the type + is checked.""" spec_part = self.find_spec_part(identifier) - check_type(spec_part, value) + if spec_part != None: + check_type(spec_part, value) isc.cc.data.set(self._local_changes, identifier, value) - def get_config_item_list(self, identifier = None): + def get_config_item_list(self, identifier = None, recurse = False): """Returns a list of strings containing the item_names of the child items at the given identifier. If no identifier is specified, returns a list of module names. The first part of @@ -370,6 +392,12 @@ class MultiConfigData: module name""" if identifier: spec = self.find_spec_part(identifier) - return spec_name_list(spec, identifier + "/") + return spec_name_list(spec, identifier + "/", recurse) else: - return self._specifications.keys() + if recurse: + id_list = [] + for module in self._specifications: + id_list.extend(spec_name_list(self._specifications[module], module, recurse)) + return id_list + else: + return list(self._specifications.keys()) diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index a5e383a017..19a8e5a879 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -28,13 +28,261 @@ class TestConfigData(unittest.TestCase): self.data_path = os.environ['CONFIG_TESTDATA_PATH'] else: self.data_path = "../../../testdata" + spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.cd = ConfigData(spec) - def test_module_spec_from_file(self): - spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") - cd = ConfigData(spec) - self.assertEqual(cd.specification, spec) - self.assertEqual(cd.data, {}) - self.assertRaises(ConfigDataError, ConfigData, 1) + #def test_module_spec_from_file(self): + # spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") + # cd = ConfigData(spec) + # self.assertEqual(cd.specification, spec) + # self.assertEqual(cd.data, {}) + # self.assertRaises(ConfigDataError, ConfigData, 1) + + def test_check_type(self): + config_spec = self.cd.get_module_spec().get_config_spec() + spec_part = find_spec_part(config_spec, "item1") + check_type(spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) + + spec_part = find_spec_part(config_spec, "item2") + check_type(spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) + + spec_part = find_spec_part(config_spec, "item3") + check_type(spec_part, True) + check_type(spec_part, False) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) + + spec_part = find_spec_part(config_spec, "item4") + check_type(spec_part, "asdf") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) + + spec_part = find_spec_part(config_spec, "item5") + check_type(spec_part, ["a", "b"]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) + + spec_part = find_spec_part(config_spec, "item6") + check_type(spec_part, { "value1": "aaa", "value2": 2 }) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + #self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "value1": 1 }) + + def test_find_spec_part(self): + config_spec = self.cd.get_module_spec().get_config_spec() + spec_part = find_spec_part(config_spec, "item1") + self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part) + self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item") + self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item/multilevel") + spec_part = find_spec_part(config_spec, "item6/value1") + #print(spec_part) + self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part) + + def test_spec_name_list(self): + name_list = spec_name_list(self.cd.get_module_spec().get_config_spec()) + self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list) + name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True) + self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list) + + def test_get_value(self): + value, default = self.cd.get_value("item1") + self.assertEqual(1, value) + self.assertEqual(True, default) + value, default = self.cd.get_value("item2") + self.assertEqual(1.1, value) + self.assertEqual(True, default) + value, default = self.cd.get_value("item3") + self.assertEqual(True, value) + self.assertEqual(True, default) + value, default = self.cd.get_value("item4") + self.assertEqual("test", value) + self.assertEqual(True, default) + value, default = self.cd.get_value("item5") + self.assertEqual(["a", "b"], value) + self.assertEqual(True, default) + value, default = self.cd.get_value("item6") + self.assertEqual({}, value) + self.assertEqual(True, default) + self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "no_such_item") + value, default = self.cd.get_value("item6/value2") + self.assertEqual(None, value) + self.assertEqual(False, default) + + def test_set_local_config(self): + self.cd.set_local_config({"item1": 2}) + value, default = self.cd.get_value("item1") + self.assertEqual(2, value) + self.assertEqual(False, default) + + def test_get_local_config(self): + local_config = self.cd.get_local_config() + self.assertEqual({}, local_config) + my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] } + self.cd.set_local_config(my_config) + self.assertEqual(my_config, self.cd.get_local_config()) + + def test_get_item_list(self): + name_list = self.cd.get_item_list() + self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list) + name_list = self.cd.get_item_list("", True) + self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list) + name_list = self.cd.get_item_list("item6", False) + self.assertEqual(['item6/value1', 'item6/value2'], name_list) + + def test_get_full_config(self): + full_config = self.cd.get_full_config() + self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5/": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config) + my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] } + self.cd.set_local_config(my_config) + full_config = self.cd.get_full_config() + self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5/": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config) + +class TestMultiConfigData(unittest.TestCase): + def setUp(self): + if 'CONFIG_TESTDATA_PATH' in os.environ: + self.data_path = os.environ['CONFIG_TESTDATA_PATH'] + else: + self.data_path = "../../../testdata" + self.mcd = MultiConfigData() + + def test_init(self): + self.assertEqual({}, self.mcd._specifications) + self.assertEqual({}, self.mcd._current_config) + self.assertEqual({}, self.mcd._local_changes) + + def test_set_specification(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") + self.mcd.set_specification(module_spec) + self.assert_(module_spec.get_module_name() in self.mcd._specifications) + self.assertEquals(module_spec, self.mcd._specifications[module_spec.get_module_name()]) + + def test_get_module_spec(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") + self.mcd.set_specification(module_spec) + module_spec2 = self.mcd.get_module_spec(module_spec.get_module_name()) + self.assertEqual(module_spec, module_spec2) + module_spec3 = self.mcd.get_module_spec("no_such_module") + self.assertEqual(None, module_spec3) + + def test_find_spec_part(self): + spec_part = self.mcd.find_spec_part("Spec2/item1") + self.assertEqual(None, spec_part) + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + spec_part = self.mcd.find_spec_part("Spec2/item1") + self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part) + + def test_get_local_changes(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + local_changes = self.mcd.get_local_changes() + self.assertEqual({}, local_changes) + self.mcd.set_value("Spec2/item1", 2) + local_changes = self.mcd.get_local_changes() + self.assertEqual({"Spec2": { "item1": 2}}, local_changes) + + + def test_clear_local_changes(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + self.mcd.set_value("Spec2/item1", 2) + self.mcd.clear_local_changes() + local_changes = self.mcd.get_local_changes() + self.assertEqual({}, local_changes) + pass + + def test_get_local_value(self): + value = self.mcd.get_local_value("Spec2/item1") + self.assertEqual(None, value) + self.mcd.set_value("Spec2/item1", 2) + value = self.mcd.get_local_value("Spec2/item1") + self.assertEqual(2, value) + + def test_get_current_value(self): + value = self.mcd.get_current_value("Spec2/item1") + self.assertEqual(None, value) + self.mcd._current_config = { "Spec2": { "item1": 3 } } + value = self.mcd.get_current_value("Spec2/item1") + self.assertEqual(3, value) + pass + + def test_get_default_value(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + value = self.mcd.get_default_value("Spec2/item1") + self.assertEqual(1, value) + value = self.mcd.get_default_value("Spec2/item6/value1") + self.assertEqual('default', value) + value = self.mcd.get_default_value("Spec2/item6/value2") + self.assertEqual(None, value) + value = self.mcd.get_default_value("Spec2/no_such_item/asdf") + self.assertEqual(None, value) + + def test_get_value(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + self.mcd.set_value("Spec2/item1", 2) + value,status = self.mcd.get_value("Spec2/item1") + self.assertEqual(2, value) + self.assertEqual(MultiConfigData.LOCAL, status) + value,status = self.mcd.get_value("Spec2/item2") + self.assertEqual(1.1, value) + self.assertEqual(MultiConfigData.DEFAULT, status) + self.mcd._current_config = { "Spec2": { "item3": False } } + value,status = self.mcd.get_value("Spec2/item3") + self.assertEqual(False, value) + self.assertEqual(MultiConfigData.CURRENT, status) + value,status = self.mcd.get_value("Spec2/no_such_item") + self.assertEqual(None, value) + self.assertEqual(MultiConfigData.NONE, status) + + def test_get_value_maps(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + maps = self.mcd.get_value_maps() + self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec2', 'value': None, 'modified': False}], maps) + + def test_set_value(self): + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + self.mcd.set_value("Spec2/item1", 2) + self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf") + self.mcd.set_value("Spec2/no_such_item", 4) + + def test_get_config_item_list(self): + config_items = self.mcd.get_config_item_list() + self.assertEqual([], config_items) + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + self.mcd.set_specification(module_spec) + config_items = self.mcd.get_config_item_list() + self.assertEqual(['Spec2'], config_items) + config_items = self.mcd.get_config_item_list("Spec2") + self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items) + config_items = self.mcd.get_config_item_list("Spec2", True) + self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items) if __name__ == '__main__': unittest.main() From d43981825ab3ac7dfd179fb8c2d329033a0f774a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 22 Feb 2010 16:06:48 +0000 Subject: [PATCH 52/81] text description on how to use config stuff git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@912 e5f2f494-b856-4b98-b285-d166d9295462 --- doc/Doxyfile | 2 +- src/lib/config/cpp/ccsession.h | 1 + src/lib/config/documentation.txt | 128 +++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/lib/config/documentation.txt diff --git a/doc/Doxyfile b/doc/Doxyfile index c6e3e31dbc..44e0ef7833 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -568,7 +568,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../src/lib/cc/cpp +INPUT = ../src/lib/cc/cpp ../src/lib/config/cpp # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index 1107959b37..b9d428b0c7 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -85,6 +85,7 @@ public: */ void set_command_handler(isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)) { command_handler_ = command_handler; }; + const ElementPtr getConfig() { return config_; } private: void read_module_specification(const std::string& filename); diff --git a/src/lib/config/documentation.txt b/src/lib/config/documentation.txt new file mode 100644 index 0000000000..34f5db33e0 --- /dev/null +++ b/src/lib/config/documentation.txt @@ -0,0 +1,128 @@ +Configuration and commands in BIND10 + +Introduction +Overview +C++ API for modules +Python API for modules +Specification files + + +Introduction +------------ + +One of the goals of BIND 10 was to have 'live' configuration; to be able to change settings on the fly, and have the system remember those settings automatically, much like, for instance, a router operates. + +In order for this to work, it is important that all modules have a way of dynamically handling configuration updates. The way this works is explained in this document. + + +Overview +-------- + +Central to the configuration part is the Configuraion Manager b10-cfgmgr. The configuration manager loads any existing configuration, receives configuration updates from user interfaces, and notifies modules about configuration updates. + +UI <---UIAPI---> Configuration Manager <---ModuleAPI---> Module + <---ModuleAPI---> Module + <---ModuleAPI---> Module + +When a configuration changes comes in from a UI, the configuration manager +sends a message to the channel of the module of which the configuration is a part of. +Through the Module API, the module automatically picks this up, and validates it. + +If it doesn't validate, an error is sent back to the manager, and subsequently to the UI (though this is not implemented yet). + +If it does, a callback function specified by the module itself is called, with the new non-default configuration values. + +We are planning to do a sort of diff-like thing here, but the current version only handles full non-default configuration values. + +The callback function returns a messge containing either success or failure, and on success, the new configuration is locally stored in the modules session. Plans are to make this optional, so that modules have two choices; they can have the configuration stored for random access later, or they can run through the configuration when there is a changes, modify their internal structures, and then drop the full configuration. This makes handling configuration updates more complicated, but is more efficient assuming that configuration values are much more often read than written. + +Commands are handled in a similar way, but do not go through the configuration manager. + + +C++ API For modules +------------------- + +The important class for modules is isc::config::ModuleCCSession; this is the class that manages the connection to the command channel, stores the current configuration, validates new configurations, and calls callback functions as needed. +[link to ModuleCCSession doxygen html] + +Upon initialization, the module provides it with a path to a specification file. This specification file contains information about the module, the configuration option the module has, and the direct commands the modules accepts. See the chapter 'specification files' for more information on these. + +The module also needs to provide two callback functions; one for handling configuration updates, and one for handling commands. + +The function for handling configuration updates has the following signature: +isc::data::ElementPtr my_config_handler(isc::data::ElementPtr new_config); +[link to Element doxygen html] + +The new_config is a ElementPtr pointing to a MapElement containing data in the form as specified by the specification file. It only contains non-default values, though that includes values that are set by the administrator which happens to be the same as the default). + +The module can walk through this set and alter it's behaviour accordingly if necessary. It can also simply return success (see below) and reference the needed configuration values directly when necessary by calling get_config_value(std::string identifier). + +The callback function must return an answer message, which is created with isc::config::createAnswer(). For successful handling of the configuration, it should return the result of createAnswer(0) (0 being the result code for success). If there is a problem, the function can return the result of createAnswer(non-zero, "string_with_error_message"). In this case, the new configuration is not stored, and the error is fed back to the configuration manager. + +Direct commands work much the same way, only in this case the answer returned can also contain an ElementPtr with data specific to the command. + +The checking of new commands or configuration updates must be done manually, with the checkCommand() function. If this function is called, the moduleccsession class checks the command channel for any relevant messages, and handles them accordingly. + + +Python API for modules +---------------------- + +The class to use in python modules is isc.config.ccsession.ModuleCCSession +[link to doxygen python version] + +Again, the module initializes it with the path to a specification file, and two callback functions. + +It works much the same as the C++ version. + +The callback function for configuration updates has the form +answer my_config_handler(new_config) + +Since python has dynamic typing, there is no specific class for the data that is passed to the handler, but it is a dict containing data as specified in the specification file. + +There are however a few convenience functions that can be found in the isc.config.data module. + +The answer can be created with isc.config.ccsession.create_answer(rcode, message), where rcode=0 is interpreted as success, and message can contain an error message for rcode!=0 results. + +The function to trigger update checks is check_command() + + +Specification files +------------------- + +There is only 1 real mandatory element in the specification, and that is the name of the module. + +The simplest specification file therefore looks like this: +{ + "module_spec": { + "module_name": "my_module" + } +} + +This is the specification for a module that has no commands and no configuration options. + +my_module is the name of the module, and also the name of the command channel it will automatically subscribe to. + +To add a simple configuration option, let's say an int, we make it the following: +{ + "module_spec": { + "module_name": "my_module" + "config_data": [ + { "item_name": "some_number", + "item_type": "integer", + "item_optional": False, + "item_default": 123 + } + ] + } +} + +"config_data" contains a list of elements of the form +{ "item_name": "name" + "item_type": "integer|real|boolean|string|list|map" + "item_optional": True|False + "item_default": +} + +You can provide as much options as you need, and can also make compound elements +through "list_item_spec" for lists and "map_item_spec" for maps. See [link] for the +full documentation on specification files. From b5d2ddbea189ffba3215521eb5499443ea2ac9db Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 22 Feb 2010 22:12:31 +0000 Subject: [PATCH 53/81] added isc::config::ConfigData class for easier client-side usage of configuration values (not completely done yet) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@915 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/Makefile.am | 4 +- src/lib/config/cpp/ccsession.cc | 6 +- src/lib/config/cpp/ccsession.h | 6 +- src/lib/config/cpp/config_data.cc | 180 ++++++++++++++++++++ src/lib/config/cpp/config_data.h | 56 ++++++ src/lib/config/cpp/config_data_unittests.cc | 103 +++++++++++ src/lib/config/cpp/module_spec.cc | 28 +-- src/lib/config/cpp/module_spec.h | 8 +- src/lib/config/cpp/module_spec_unittests.cc | 15 +- 9 files changed, 376 insertions(+), 30 deletions(-) create mode 100644 src/lib/config/cpp/config_data.cc create mode 100644 src/lib/config/cpp/config_data.h create mode 100644 src/lib/config/cpp/config_data_unittests.cc diff --git a/src/lib/config/cpp/Makefile.am b/src/lib/config/cpp/Makefile.am index e182e8d300..2226d4bcbd 100644 --- a/src/lib/config/cpp/Makefile.am +++ b/src/lib/config/cpp/Makefile.am @@ -1,14 +1,14 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/ext -Wall -Werror lib_LIBRARIES = libcfgclient.a -libcfgclient_a_SOURCES = module_spec.h module_spec.cc ccsession.cc ccsession.h +libcfgclient_a_SOURCES = config_data.h config_data.cc module_spec.h module_spec.cc ccsession.cc ccsession.h CLEANFILES = *.gcno *.gcda TESTS = if HAVE_GTEST TESTS += run_unittests -run_unittests_SOURCES = module_spec_unittests.cc run_unittests.cc +run_unittests_SOURCES = module_spec_unittests.cc config_data_unittests.cc run_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDADD = libcfgclient.a $(GTEST_LDADD) diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 306eb8ea6b..3810aec444 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -135,7 +135,7 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name, read_module_specification(spec_file_name); sleep(1); - module_name_ = module_specification_.getFullSpec()->get("module_spec")->get("module_name")->stringValue(); + module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue(); config_handler_ = config_handler; command_handler_ = command_handler; @@ -150,7 +150,9 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name, //session_.subscribe("Boss", "*"); //session_.subscribe("statistics", "*"); // send the data specification - session_.group_sendmsg(module_specification_.getFullSpec(), "ConfigManager"); + ElementPtr spec_msg = Element::createFromString("{}"); + spec_msg->set("module_spec", module_specification_.getFullSpec()); + session_.group_sendmsg(spec_msg, "ConfigManager"); session_.group_recvmsg(env, answer, false); // get any stored configuration from the manager diff --git a/src/lib/config/cpp/ccsession.h b/src/lib/config/cpp/ccsession.h index b9d428b0c7..fe05d5728f 100644 --- a/src/lib/config/cpp/ccsession.h +++ b/src/lib/config/cpp/ccsession.h @@ -37,7 +37,11 @@ public: isc::Exception(file, line, what) {} }; - +/// +/// \brief This modules keeps a connection to the command channel, +/// holds configuration information, and handles messages from +/// the command channel +/// class ModuleCCSession { public: /** diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc new file mode 100644 index 0000000000..620a912700 --- /dev/null +++ b/src/lib/config/cpp/config_data.cc @@ -0,0 +1,180 @@ +// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id$ + +#include "config_data.h" + +#include + +#include +#include + +using namespace isc::data; + +namespace isc { +namespace config { + +ElementPtr +find_spec_part(ElementPtr spec, const std::string& identifier) +{ + //std::cout << "[XX] find_spec_part" << std::endl; + if (!spec) { return ElementPtr(); } + //std::cout << "in: " << spec << std::endl; + ElementPtr spec_part = spec; + if (identifier == "") { + //std::cout << "[XX] empty id" << std::endl; + return ElementPtr(); + } + std::string id = identifier; + size_t sep = id.find('/'); + while(sep != std::string::npos) { + std::string part = id.substr(0, sep); + //std::cout << "[XX] id part: " << part << std::endl; + if (spec_part->getType() == Element::list) { + bool found = false; + BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name") && + list_el->get("item_name")->stringValue() == part) { + spec_part = list_el->get("item_name"); + found = true; + } + } + if (!found) { + // raise exception? + return ElementPtr(); + } + } else if (spec_part->getType() == Element::map) { + if (spec_part->contains("map_item_spec")) { + bool found = false; + BOOST_FOREACH(ElementPtr list_el, spec_part->get("map_item_spec")->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name") && + list_el->get("item_name")->stringValue() == part) { + spec_part = list_el->get("item_name"); + found = true; + } + } + if (!found) { + // raise exception? + return ElementPtr(); + } + } + } + id = id.substr(sep + 1); + } + if (id != "" && id != "/") { + if (spec_part->getType() == Element::list) { + bool found = false; + BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name") && + list_el->get("item_name")->stringValue() == id) { + spec_part = list_el; + found = true; + } + } + if (!found) { + // raise exception? + return ElementPtr(); + } + } else if (spec_part->getType() == Element::map) { + if (spec_part->contains("map_item_spec")) { + bool found = false; + BOOST_FOREACH(ElementPtr list_el, spec_part->get("map_item_spec")->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name") && + list_el->get("item_name")->stringValue() == id) { + spec_part = list_el; + found = true; + } + } + if (!found) { + // raise exception? + return ElementPtr(); + } + } + } + } + return spec_part; +} + +ElementPtr +ConfigData::getValue(const std::string& identifier) +{ + bool fake; + return getValue(fake, identifier); +} + +ElementPtr +ConfigData::getValue(bool& is_default, const std::string& identifier) +{ + ElementPtr value = _config->find(identifier); + if (!value) { + ElementPtr spec_part = find_spec_part(_module_spec.getConfigSpec(), identifier); + if (spec_part) { + value = spec_part->get("item_default"); + is_default = true; + } else { + // we should raise an error here + dns_throw(DataNotFoundError, "identifier not found"); + } + } else { + is_default = false; + } + return value; +} + +void +spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool recurse = false) +{ + if (spec_part->getType() == Element::list) { + BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name")) { + result->add(Element::create(prefix + "/" + list_el->get("item_name")->stringValue())); + } + } + } else if (spec_part->getType() == Element::map && + spec_part->contains("map_item_spec") + ) { + if (recurse) { + spec_name_list(result, spec_part->get("map_item_spec"), prefix + "/" + spec_part->get("item_name")->stringValue(), recurse); + } else { + result->add(Element::create(prefix + "/" + spec_part->get("item_name")->stringValue())); + } + } +} + +ElementPtr +ConfigData::getItemList(const std::string& identifier, bool recurse) +{ + ElementPtr result = Element::createFromString("[]"); + ElementPtr spec_part = getModuleSpec().getConfigSpec(); + if (identifier != "" && identifier != "/") { + spec_part = find_spec_part(spec_part, identifier); + } + spec_name_list(result, spec_part, identifier, recurse); + return result; +} + +ElementPtr +ConfigData::getFullConfig() +{ + return ElementPtr(); +} + +} +} diff --git a/src/lib/config/cpp/config_data.h b/src/lib/config/cpp/config_data.h new file mode 100644 index 0000000000..5520bf15fa --- /dev/null +++ b/src/lib/config/cpp/config_data.h @@ -0,0 +1,56 @@ +// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id$ + +#ifndef __CONFIG_DATA_H +#define __CONFIG_DATA_H 1 + +#include +#include + +#include +#include + +namespace isc { +namespace config { + +class DataNotFoundError : public isc::Exception { +public: + DataNotFoundError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +class ConfigData { +public: + ConfigData() { _config = Element::createFromString("{}"); }; + ConfigData(const ModuleSpec& module_spec) : _module_spec(module_spec) { _config = Element::createFromString("{}"); } + + ElementPtr getValue(const std::string& identifier); + ElementPtr getValue(bool &is_default, const std::string& identifier); + const ModuleSpec getModuleSpec() { return _module_spec; }; + void setModuleSpec(ModuleSpec module_spec) { _module_spec = module_spec; }; + void setLocalConfig(ElementPtr config) { _config = config; } + ElementPtr getLocalConfig() { return _config; } + ElementPtr getItemList(const std::string& identifier, bool recurse = false); + ElementPtr getFullConfig(); + +private: + ElementPtr _config; + ModuleSpec _module_spec; +}; + +} +} +#endif diff --git a/src/lib/config/cpp/config_data_unittests.cc b/src/lib/config/cpp/config_data_unittests.cc new file mode 100644 index 0000000000..90b486fa8e --- /dev/null +++ b/src/lib/config/cpp/config_data_unittests.cc @@ -0,0 +1,103 @@ + +// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id$ + +#include + +#include "config_data.h" +#include "data_def_unittests_config.h" + +#include + +using namespace isc::data; +using namespace isc::config; + +ConfigData +setupSpec2() +{ + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + return ConfigData(spec2); +} + +TEST(ConfigData, Creation) { + ConfigData cd = setupSpec2(); + EXPECT_TRUE(true); +} + +TEST(ConfigData, getValue) { + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ConfigData cd = ConfigData(spec2); + //std::cout << "[XX] SPEC2: " << cd.getModuleSpec().getFullSpec() << std::endl; + bool is_default; + //ElementPtr value = cd.getValue(is_default, "item1"); + EXPECT_EQ(1, cd.getValue(is_default, "item1")->intValue()); + EXPECT_TRUE(is_default); + EXPECT_EQ(1.1, cd.getValue(is_default, "item2")->doubleValue()); + EXPECT_TRUE(is_default); + EXPECT_TRUE(cd.getValue(is_default, "item3")->boolValue()); + EXPECT_TRUE(is_default); + EXPECT_EQ("test", cd.getValue(is_default, "item4")->stringValue()); + EXPECT_TRUE(is_default); + EXPECT_EQ("a", cd.getValue(is_default, "item5")->get(0)->stringValue()); + EXPECT_TRUE(is_default); + EXPECT_EQ("b", cd.getValue(is_default, "item5")->get(1)->stringValue()); + EXPECT_TRUE(is_default); + EXPECT_EQ("{}", cd.getValue(is_default, "item6")->str()); + EXPECT_TRUE(is_default); +} + +TEST(ConfigData, setLocalConfig) { + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ConfigData cd = ConfigData(spec2); + bool is_default; + + ElementPtr my_config = Element::createFromString("{\"item1\": 2}"); + ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }"); + + EXPECT_EQ("{}", cd.getValue("item6")->str()); + + cd.setLocalConfig(my_config); + EXPECT_EQ(2, cd.getValue(is_default, "item1")->intValue()); + EXPECT_FALSE(is_default); + EXPECT_EQ("{}", cd.getValue("item6")->str()); + EXPECT_EQ(1.1, cd.getValue(is_default, "item2")->doubleValue()); + EXPECT_TRUE(is_default); + + cd.setLocalConfig(my_config2); + EXPECT_EQ("{\"value1\": \"a\"}", cd.getValue("item6")->str()); +} + +TEST(ConfigData, getLocalConfig) { + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ConfigData cd = ConfigData(spec2); + EXPECT_EQ("{}", cd.getLocalConfig()->str()); + + ElementPtr my_config = Element::createFromString("{\"item1\": 2}"); + cd.setLocalConfig(my_config); + EXPECT_EQ("{\"item1\": 2}", cd.getLocalConfig()->str()); + + ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }"); + cd.setLocalConfig(my_config2); + EXPECT_EQ("{\"item6\": {\"value1\": \"a\"}}", cd.getLocalConfig()->str()); +} + +TEST(ConfigData, getItemList) { + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ConfigData cd = ConfigData(spec2); + + //EXPECT_EQ("", cd.getItemList("")->str()); +} + diff --git a/src/lib/config/cpp/module_spec.cc b/src/lib/config/cpp/module_spec.cc index 27314f38d1..dc38ca2f4d 100644 --- a/src/lib/config/cpp/module_spec.cc +++ b/src/lib/config/cpp/module_spec.cc @@ -160,11 +160,7 @@ check_data_specification(const ElementPtr& spec) { static void check_module_specification(const ElementPtr& def) { - if (!def->contains("module_spec")) { - throw ModuleSpecError("Data specification does not contain module_spec element"); - } else { - check_data_specification(def->get("module_spec")); - } + check_data_specification(def); } // @@ -183,7 +179,7 @@ ModuleSpec::ModuleSpec(ElementPtr module_spec_element, } const ElementPtr -ModuleSpec::getCommandsSpec() +ModuleSpec::getCommandsSpec() const { if (module_specification->contains("commands")) { return module_specification->get("commands"); @@ -193,7 +189,7 @@ ModuleSpec::getCommandsSpec() } const ElementPtr -ModuleSpec::getConfigSpec() +ModuleSpec::getConfigSpec() const { if (module_specification->contains("config_data")) { return module_specification->get("config_data"); @@ -203,7 +199,7 @@ ModuleSpec::getConfigSpec() } const std::string -ModuleSpec::getModuleName() +ModuleSpec::getModuleName() const { return module_specification->get("module_name")->stringValue(); } @@ -211,14 +207,14 @@ ModuleSpec::getModuleName() bool ModuleSpec::validate_config(const ElementPtr data, const bool full) { - ElementPtr spec = module_specification->find("module_spec/config_data"); + ElementPtr spec = module_specification->find("config_data"); return validate_spec_list(spec, data, full, ElementPtr()); } bool ModuleSpec::validate_config(const ElementPtr data, const bool full, ElementPtr errors) { - ElementPtr spec = module_specification->find("module_spec/config_data"); + ElementPtr spec = module_specification->find("config_data"); return validate_spec_list(spec, data, full, errors); } @@ -236,14 +232,22 @@ moduleSpecFromFile(const std::string& file_name, const bool check) } ElementPtr module_spec_element = Element::createFromString(file, file_name); - return ModuleSpec(module_spec_element, check); + if (module_spec_element->contains("module_spec")) { + return ModuleSpec(module_spec_element->get("module_spec"), check); + } else { + throw ModuleSpecError("No module_spec in specification"); + } } ModuleSpec moduleSpecFromFile(std::ifstream& in, const bool check) throw(ParseError, ModuleSpecError) { ElementPtr module_spec_element = Element::createFromString(in); - return ModuleSpec(module_spec_element, check); + if (module_spec_element->contains("module_spec")) { + return ModuleSpec(module_spec_element->get("module_spec"), check); + } else { + throw ModuleSpecError("No module_spec in specification"); + } } diff --git a/src/lib/config/cpp/module_spec.h b/src/lib/config/cpp/module_spec.h index 97059e82fb..908eef9120 100644 --- a/src/lib/config/cpp/module_spec.h +++ b/src/lib/config/cpp/module_spec.h @@ -62,20 +62,20 @@ namespace isc { namespace config { /// ElementPtr, returns an empty ElementPtr if there is none /// \return ElementPtr Shared pointer to the commands /// part of the specification - const ElementPtr getCommandsSpec(); + const ElementPtr getCommandsSpec() const; /// Returns the configuration part of the specification as an /// ElementPtr /// \return ElementPtr Shared pointer to the configuration /// part of the specification - const ElementPtr getConfigSpec(); + const ElementPtr getConfigSpec() const; /// Returns the full module specification as an ElementPtr /// \return ElementPtr Shared pointer to the specification - const ElementPtr getFullSpec() { return module_specification; }; + const ElementPtr getFullSpec() const { return module_specification; }; /// Returns the module name as specified by the specification - const std::string getModuleName(); + const std::string getModuleName() const; // returns true if the given element conforms to this data // configuration specification diff --git a/src/lib/config/cpp/module_spec_unittests.cc b/src/lib/config/cpp/module_spec_unittests.cc index 29ee4fc8ee..273e25c88f 100644 --- a/src/lib/config/cpp/module_spec_unittests.cc +++ b/src/lib/config/cpp/module_spec_unittests.cc @@ -48,12 +48,10 @@ TEST(ModuleSpec, ReadingSpecfiles) { // Tests whether we can open specfiles and if we get the // right parse errors ModuleSpec dd = moduleSpecFromFile(specfile("spec1.spec")); - EXPECT_EQ(dd.getFullSpec()->get("module_spec") - ->get("module_name") - ->stringValue(), "Spec1"); + EXPECT_EQ(dd.getFullSpec()->get("module_name") + ->stringValue(), "Spec1"); dd = moduleSpecFromFile(specfile("spec2.spec")); - EXPECT_EQ(dd.getFullSpec()->get("module_spec") - ->get("config_data")->size(), 6); + EXPECT_EQ(dd.getFullSpec()->get("config_data")->size(), 6); module_spec_error("doesnotexist", "Error opening ", specfile("doesnotexist"), @@ -62,9 +60,8 @@ TEST(ModuleSpec, ReadingSpecfiles) { std::ifstream file; file.open(specfile("spec1.spec").c_str()); dd = moduleSpecFromFile(file); - EXPECT_EQ(dd.getFullSpec()->get("module_spec") - ->get("module_name") - ->stringValue(), "Spec1"); + EXPECT_EQ(dd.getFullSpec()->get("module_name") + ->stringValue(), "Spec1"); } TEST(ModuleSpec, SpecfileItems) { @@ -97,7 +94,7 @@ TEST(ModuleSpec, SpecfileConfigData) module_spec_error("spec7.spec", "module_name missing in {}"); module_spec_error("spec8.spec", - "Data specification does not contain module_spec element"); + "No module_spec in specification"); module_spec_error("spec16.spec", "config_data is not a list of elements"); module_spec_error("spec21.spec", From c4cf50a3af45d067b5bed4ceb8aae8e737bda154 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Tue, 23 Feb 2010 07:52:57 +0000 Subject: [PATCH 54/81] - added detaild documentation - some more tests - overall cleanup git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@917 e5f2f494-b856-4b98-b285-d166d9295462 --- doc/Doxyfile | 2 +- src/lib/dns/cpp/rdata.h | 1 + src/lib/dns/cpp/rrset.cc | 78 +++- src/lib/dns/cpp/rrset.h | 697 ++++++++++++++++++++++++++---- src/lib/dns/cpp/rrset_unittest.cc | 22 + 5 files changed, 690 insertions(+), 110 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index c6e3e31dbc..2819031270 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -568,7 +568,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../src/lib/cc/cpp +INPUT = ../src/lib/cc/cpp ../src/lib/dns/cpp # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/src/lib/dns/cpp/rdata.h b/src/lib/dns/cpp/rdata.h index d9d62128e4..f18656ec73 100644 --- a/src/lib/dns/cpp/rdata.h +++ b/src/lib/dns/cpp/rdata.h @@ -65,6 +65,7 @@ public: class Rdata; typedef boost::shared_ptr RdataPtr; +typedef boost::shared_ptr ConstRdataPtr; /// Abstract RDATA class class Rdata { diff --git a/src/lib/dns/cpp/rrset.cc b/src/lib/dns/cpp/rrset.cc index ef4b433646..ee07b80b51 100644 --- a/src/lib/dns/cpp/rrset.cc +++ b/src/lib/dns/cpp/rrset.cc @@ -46,16 +46,20 @@ AbstractRRset::toText() const string s; RdataIteratorPtr it = getRdataIterator(); - for (it->first(); !it->isLast(); it->next()) { - s += getName().toText() + " " + - getTTL().toText() + " " + - getClass().toText() + " " + - getType().toText() + " " + - it->getCurrent().toText() + "\n"; + it->first(); + if (it->isLast()) { + dns_throw(EmptyRRset, "ToText() is attempted for an empty RRset"); } + do { + s += getName().toText() + " " + getTTL().toText() + " " + + getClass().toText() + " " + getType().toText() + " " + + it->getCurrent().toText() + "\n"; + it->next(); + } while (!it->isLast()); + return (s); -} +} namespace { template @@ -65,9 +69,14 @@ rrsetToWire(const AbstractRRset& rrset, T& output) unsigned int n = 0; RdataIteratorPtr it = rrset.getRdataIterator(); + it->first(); + if (it->isLast()) { + dns_throw(EmptyRRset, "ToWire() is attempted for an empty RRset"); + } + // sort the set of Rdata based on rrset-order and sortlist, and possible // other options. Details to be considered. - for (it->first(); !it->isLast(); it->next(), ++n) { + do { rrset.getName().toWire(output); rrset.getType().toWire(output); rrset.getClass().toWire(output); @@ -77,7 +86,10 @@ rrsetToWire(const AbstractRRset& rrset, T& output) output.skip(sizeof(uint16_t)); // leave the space for RDLENGTH it->getCurrent().toWire(output); output.writeUint16At(output.getLength() - pos - sizeof(uint16_t), pos); - } + + it->next(); + ++n; + } while (!it->isLast()); return (n); } @@ -102,7 +114,10 @@ operator<<(ostream& os, const AbstractRRset& rrset) return (os); } -struct BasicRRsetImpl { +/// \brief This encapsulates the actual implementation of the \c BasicRRset +/// class. It's hidden from applications. +class BasicRRsetImpl { +public: BasicRRsetImpl(const Name& name, const RRClass& rrclass, const RRType& rrtype, const RRTTL& ttl) : name_(name), rrclass_(rrclass), rrtype_(rrtype), ttl_(ttl) {} @@ -110,7 +125,10 @@ struct BasicRRsetImpl { RRClass rrclass_; RRType rrtype_; RRTTL ttl_; - vector rdatalist_; + // XXX: "list" is not a good name: It in fact isn't a list; more conceptual + // name than a data structure name is generally better. But since this + // is only used in the internal implementation we'll live with it. + vector rdatalist_; }; BasicRRset::BasicRRset(const Name& name, const RRClass& rrclass, @@ -125,11 +143,17 @@ BasicRRset::~BasicRRset() } void -BasicRRset::addRdata(const RdataPtr rdata) +BasicRRset::addRdata(ConstRdataPtr rdata) { impl_->rdatalist_.push_back(rdata); } +void +BasicRRset::addRdata(const Rdata& rdata) +{ + AbstractRRset::addRdata(rdata); +} + unsigned int BasicRRset::getRdataCount() const { @@ -160,18 +184,42 @@ BasicRRset::getTTL() const return (impl_->ttl_); } +void +BasicRRset::setName(const Name& name) +{ + impl_->name_ = name; +} + void BasicRRset::setTTL(const RRTTL& ttl) { impl_->ttl_ = ttl; } +string +BasicRRset::toText() const +{ + return (AbstractRRset::toText()); +} + +unsigned int +BasicRRset::toWire(OutputBuffer& buffer) const +{ + return (AbstractRRset::toWire(buffer)); +} + +unsigned int +BasicRRset::toWire(MessageRenderer& renderer) const +{ + return (AbstractRRset::toWire(renderer)); +} + namespace { class BasicRdataIterator : public RdataIterator { private: BasicRdataIterator() {} public: - BasicRdataIterator(const std::vector& datavector) : + BasicRdataIterator(const std::vector& datavector) : datavector_(&datavector) {} ~BasicRdataIterator() {} virtual void first() { it_ = datavector_->begin(); } @@ -179,8 +227,8 @@ public: virtual const rdata::Rdata& getCurrent() const { return (**it_); } virtual bool isLast() const { return (it_ == datavector_->end()); } private: - const std::vector* datavector_; - std::vector::const_iterator it_; + const std::vector* datavector_; + std::vector::const_iterator it_; }; } diff --git a/src/lib/dns/cpp/rrset.h b/src/lib/dns/cpp/rrset.h index f2540b370a..fbea6fb3db 100644 --- a/src/lib/dns/cpp/rrset.h +++ b/src/lib/dns/cpp/rrset.h @@ -22,148 +22,657 @@ #include +#include + #include "rdata.h" namespace isc { namespace dns { +/// +/// \brief A standard DNS module exception that is thrown if an RRset object +/// does not contain any RDATA where required. +/// +class EmptyRRset : public Exception { +public: + EmptyRRset(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +// forward declarations class Name; class RRType; class RRClass; class RRTTL; class OututBuffer; class MessageRenderer; - class AbstractRRset; -typedef boost::shared_ptr RRsetPtr; class BasicRRset; -typedef BasicRRset RRset; +class RdataIterator; class BasicRRsetImpl; -class RdataIterator; +/// \brief A pointer-like type pointing to an \c AbstractRRset object. +/// +/// This type is commonly used as an argument of various functions defined +/// in this library in order to handle RRsets in a polymorphic manner. +typedef boost::shared_ptr RRsetPtr; + +/// \brief A pointer-like type pointing to an (immutable) \c AbstractRRset +/// object. +/// +/// This type is commonly used as an argument of various functions defined +/// in this library in order to handle RRsets in a polymorphic manner. +typedef boost::shared_ptr ConstRRsetPtr; + +/// \brief A convenient abbreviation for the most generic derived RRset class. +typedef BasicRRset RRset; + +/// \brief A pointer-like type point to an \c RdataIterator object. typedef boost::shared_ptr RdataIteratorPtr; -/// \brief TBD +/// \brief The \c AbstractRRset class is an abstract base class that +/// models a DNS RRset. /// -/// An \c RRset object models an RRset as described in the DNS standard: -/// a set of DNS resource records (RRs) of the same type and class. +/// An object of (a specific derived class of) \c AbstractRRset +/// models an RRset as described in the DNS standard: +/// A set of DNS resource records (RRs) of the same type and class. /// The standard requires the TTL of all RRs in an RRset be the same; /// this class follows that requirement. -/// Note about duplication: we probably don't enforce the RDATA is unique -/// as a class responsibility - but may revisit the decision. + +/// Note about duplicate RDATA: RFC2181 states that it's meaningless that an +/// RRset contains two identical RRs and that name servers should suppress +/// such duplicates. +/// This class is not responsible for ensuring this requirement: For example, +/// \c addRdata() method doesn't check if there's already RDATA identical +/// to the one being added. +/// This is because such checks can be expensive, and it's often easy to +/// ensure the uniqueness requirement at the %data preparation phase +/// (e.g. when loading a zone). +/// When parsing an incoming DNS message, the uniqueness may not be guaranteed, +/// so the application needs to detect and ignore any duplicate RDATA +/// (the \c Message class of this library should provide this responsibility). /// -// This is a primary class internally used in our major software such as name -// servers. -// -// Note about terminology: there has been a discussion at the IETF namedroppers -// ML about RRset vs RRSet (case of "s"). While RFC2181 uses the latter, -// many other RFCs use the former, and most of the list members who showed -// their opinion seem to prefer RRset. We follow that preference in this -// implementation. -// -// Open Issues: -// - add more set-like operations, e.g, merge? -// - add a "sort" method? "search(find)" method? -// - BIND9 libdns has some special DNSSEC-related methods -// such as addnoqname(), addclosest(). do we need these? -// - need to check duplicate rdata in addrdata()? -// - need a comparison method? if so, should it compare -// rdata's as a set or as a list (compare each rdata one -// by one)? ldns has ldns_rr_list_compare(), which takes -// the latter approach (assuming the caller sorts the lists -// beforehand?). -// - do we need to allow the user to remove specific Rdata? -/// Looking at the BIND9 code, don't see the strong need for this at the -/// moment. +/// Another point to note is that \c AbstractRRset and its derived classes +/// allow an object to have an empty set of RDATA. +/// Even though there's no corresponding notion in the protocol specification, +/// it would be more intuitive for a container-like %data structure +/// to allow an empty set. /// +/// Since \c AbstractRRset is an abstract class, it is generally used +/// via a pointer (or pointer like object) or a reference. +/// In particular, \c RRsetPtr, a pointer like type for \c AbstractRRset, +/// is used for polymorphic RRset operations throughout this library. +/// +/// The \c AbstractRRset class is also intended to be a major customization +/// point. For example, a high performance server implementation may want +/// to define an optimized "pre-compiled" RRset and provide an optimized +/// implementation of the \c toWire() method. +/// +/// Note about design choice: In BIND9, a set of RDATA with a common tuple +/// of RR class, RR type, and TTL was represented in a structure named +/// \c rdataset. Unlike the RRset classes, an \c rdataset did not contain +/// the information of the owner name. +/// This might be advantageous if we want to handle "RRsets", that is, +/// a set of different types of RRset for the same owner name, because +/// a single "name" structure can be used for multiple RRsets, minimizing +/// %data copy and memory footprint. +/// On the other hand, it's inconvenient for API users since in many cases +/// a pair of name and an \c rdataset must be maintained. It's also counter +/// intuitive in implementing protocol operations as an RRset is often used +/// as an atomic entity in DNS protocols while an \c rdataset is a component +/// of an RRset. +/// +/// We have therefore defined the notion of RRset explicitly in our initial +/// API design. We believe memory footprint is not a big concern because +/// RRsets are generally expected to be used as temporary objects, e.g. +/// while parsing or constructing a DNS message, or searching a DNS %data +/// source; for longer term purposes such as in-memory %data source entries, +/// the corresponding %data would be represented in a different, memory +/// optimized format. As for the concern about %data copy, we believe +/// it can be mitigated by using copy-efficient implementation for the +/// \c Name class implementation, such as reference counted objects. +/// Later, We plan to perform benchmark tests later to see if this assumption +/// is valid and to revisit the design if necessary. +/// +/// Note about terminology: there has been a discussion at the IETF +/// namedroppers ML about RRset vs RRSet (case of "s") +/// [http://ops.ietf.org/lists/namedroppers/namedroppers.2009/msg02737.html]. +/// While RFC2181 uses the latter, many other RFCs use the former, +/// and most of the list members who showed their opinion seem to prefer +/// "RRset". We follow that preference in this implementation. +/// +/// The current design of \c AbstractRRset is still in flux. +/// There are many open questions in design details: +/// - support more set-like operations, e.g, merge two RRsets of the same +/// type? +/// - more convenient methods or non member utility functions, e.g. +/// "sort" and "search(find)" method? +/// - what about comparing two RRsets of the same type? If we need this, +/// should it compare rdata's as a set or as a list (i.e. compare +/// each %rdata one by one or as a whole)? c.f. NLnet Labs' +/// ldns +/// has \c ldns_rr_list_compare(), which takes the latter approach +/// (seemingly assuming the caller sorts the lists beforehand). +/// - BIND9 libdns has some special DNSSEC-related methods +/// such as \c addnoqname() or \c addclosest(). Do we need these? +/// (Probably not. We wouldn't want to make the class design too +/// monolithic.) +/// - Do we need to allow the user to remove specific Rdata? +/// Probably not, according to the current usage of the BIND9 code. class AbstractRRset { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private to make it explicit that this is a pure base class. + //@{ +private: + AbstractRRset(const AbstractRRset& source); + AbstractRRset& operator=(const AbstractRRset& source); +protected: + /// \brief The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + AbstractRRset() {} public: + /// The destructor. virtual ~AbstractRRset() {} - virtual std::string toText() const; - /// Note: perhaps we may want to add more arguments to convey optional - /// information such as an "rrset-order" policy. - /// no name compression, no truncation - virtual unsigned int toWire(OutputBuffer& buffer) const; - /// name compression when necessary, taking into account truncation - virtual unsigned int toWire(MessageRenderer& renderer) const; + //@} + + /// + /// \name Getter and Setter Methods + /// + /// These methods are generally expected to be exception free, but it's + /// not guaranteed at the interface level; + /// for example, some performance optimized derived class may manage + /// the information corresponding to the class "attributes" to get or set, + /// and may require dynamic memory allocation to execute the method. + /// Consult the derived class description to see if a specific derived + /// \c RRset class may throw an exception from these methods. + /// + /// Note that setter methods are not provided for \c RRClass and + /// \c RRType. This is intentional. Since the format and semantics of + /// \c Rdata are dependent on the RR type (and RR class for some RR types), + /// allowing dynamically modify these attributes can easily lead to a + /// bug where the RDATA and type and/or class become inconsistent. + /// We want to avoid that situation by restricting the access. + //@{ + /// \brief Returns the number of \c Rdata objects contained in the \c RRset. + /// + /// Note that an \c RRset with an empty set of \c Rdata can exist, so + /// this method may return 0. + /// + /// \return The number of \c Rdata objects contained. virtual unsigned int getRdataCount() const = 0; + + /// \brief Returns the owner name of the \c RRset. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c RRset owner name. virtual const Name& getName() const = 0; + + /// \brief Returns the RR Class of the \c RRset. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c RRset. virtual const RRClass& getClass() const = 0; + + /// \brief Returns the RR Type of the \c RRset. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c RRset. virtual const RRType& getType() const = 0; + + /// \brief Returns the TTL of the RRset. + /// + /// \return A reference to a \c RRTTL class object corresponding to the + /// TTL of the \c RRset. virtual const RRTTL& getTTL() const = 0; + + /// \brief Updates the owner name of the \c RRset. /// - /// once constructed, only TTL and the set of Rdata can be modified, - /// so \c setTTL() is the only explicit setter method. + /// \param name A reference to a \c RRTTL class object to be copied as the + /// new TTL. + virtual void setName(const Name& name) = 0; + + /// \brief Updates the TTL of the \c RRset. /// + /// \param ttl A reference to a \c RRTTL class object to be copied as the + /// new TTL. virtual void setTTL(const RRTTL& ttl) = 0; + //@} + + /// + /// \name Converter Methods + /// + /// These methods have the default implementation that can be reused by + /// derived classes. + /// Since they are defined as pure virtual, derived classes + /// that want to reuse the default implementation must explicitly + /// invoke their base class version (see the description for + /// addRdata(const rdata::Rdata&)). + /// + /// Design Note: the default implementations are defined only using + /// other public methods of the \c AbstractRRset class, and could be + /// implemented as non member functions (as some C++ textbooks suggest). + /// However, since derived classes may want to provide customized versions + /// (especially of the \c toWire() method for performance reasons) + /// we chose to define them as virtual functions, and, as a result, + /// member functions. + //@{ + /// \brief Convert the RRset to a string. + /// + /// Unlike other similar methods of this library, this method terminates + /// the resulting string with a trailing newline character. + /// (following the BIND9 convention) + /// + /// The RRset must contain some RDATA; otherwise, an exception of class + /// \c EmptyRRset will be thrown. + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toText() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// Open issue: We may want to support multiple output formats as + /// BIND9 does. For example, we might want to allow omitting the owner + /// name when possible in the context of zone dump. This is a future + /// TODO item. + /// + /// \param rrset A reference to a (derived class of) \c AbstractRRset object + /// whose content is to be converted. + /// \return A string representation of the RRset. + virtual std::string toText() const = 0; + + /// \brief Render the RRset in the wire format with name compression and + /// truncation handling. + /// + /// This method compresses the owner name of the RRset and domain names + /// used in RDATA that should be compressed. + /// In addition, this method detects the case where rendering the entire + /// RRset would cause truncation, and handles the case appropriately + /// (this is a TODO item, and not implemented in this version). + /// + /// Note: perhaps we may want to add more arguments to convey optional + /// information such as an "rrset-order" policy or how to handle truncation + /// case. This is a TODO item. + /// + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// The RRset must contain some RDATA; otherwise, an exception of class + /// \c EmptyRRset will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toWire() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \return The number of RRs rendered. If the truncation is necessary + /// this value may be different from the number of RDATA objects contained + /// in the RRset. + virtual unsigned int toWire(MessageRenderer& renderer) const = 0; + + /// \brief Render the RRset in the wire format without any compression. + /// + /// See the other toWire() description about possible exceptions. + /// + /// \param buffer An output buffer to store the wire data. + /// \return The number of RRs rendered. + virtual unsigned int toWire(OutputBuffer& buffer) const = 0; + //@} + + /// + /// \name RDATA Manipulation Methods + /// + //@{ + /// \brief Add an RDATA to the RRset (pointer version). + /// + /// This method adds the given RDATA (as a pointer-like type to a + /// derived class object of \c rdata::Rdata) to the \c RRset. + /// + /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added + /// to the \c RRset. + virtual void addRdata(rdata::ConstRdataPtr rdata) = 0; + + /// \brief Add an RDATA to the RRset (reference version). + /// + /// This method adds the given RDATA (as a reference to a + /// derived class object of \c rdata::Rdata) to the \c RRset. + /// + /// This method has the default implementation that can be reused by + /// derived classes. + /// Since this method is defined as pure virtual, derived classes + /// that want to reuse the default implementation must explicitly + /// invoke this base class version. + /// For example, if the class \c CustomizedRRset, a derived class of + /// \c AbstractRRset, wants to reuse the default implementation of + /// \c %addRdata() (reference version), it would be defined as follows: + /// \code void + /// CustomizedRRset::addRdata(const rdata::Rdata& rdata) + /// { + /// AbstractRRset::addRdata(rdata); + /// } + /// \endcode + /// + /// This method is more strictly typed than the pointer version: + /// If \c %rdata does not refer to the appropriate derived + /// \c Rdata class + /// for the \c RRType for this \c RRset, it throws an exception of class + /// \c std::bad_cast. + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// The RRset must contain some RDATA; otherwise, an exception of class + /// \c EmptyRRset will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toWire() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// The default implementation simply constructs an \c rdata::RdataPtr + /// object from a newly allocated RDATA object copying from parameter + /// \c rdata, and calls the other version of + /// \c addRdata(const rdata::RdataPtr). + /// So it is inherently less efficient than the other version. + /// Still, this version would offer a more intuitive interface and is + /// provided as such. + /// + /// \param rdata A reference to a \c rdata::RdataPtr (derived) class + /// object, a copy of which is to be added to the \c RRset. + virtual void addRdata(const rdata::Rdata& rdata) = 0; + + /// \brief Return an iterator to go through all RDATA stored in the + /// \c RRset. + /// + /// Using the design pattern terminology, \c getRdataIterator() + /// is an example of a factory method. + /// + /// Whether this method throws an exception depends on the actual + /// implementation of the derived \c AbstractRRset class, but in general + /// it will involve resource allocation and can throw a standard exception + /// if it fails. + /// + /// \return A pointer-like object pointing to the derived \c RdataIterator + /// object. + virtual RdataIteratorPtr getRdataIterator() const = 0; + //@} +}; + +/// \brief The \c RdataIterator class is an abstract base class that +/// provides an interface for accessing RDATA objects stored in an RRset. +/// +/// While different derived classes of \c AbstractRRset may maintain the RDATA +/// objects in different ways, the \c RdataIterator class provides a +/// unified interface to iterate over the RDATA objects in a polymorphic +/// manner. +/// +/// Each derived class of \c AbstractRRset is expected to provide a concrete +/// derived class of \c RdataIterator, and each derived \c RdataIterator +/// class implements the unified interface in a way specific to the +/// implementation of the corresponding derived \c AbstractRRset class. +/// Using the design pattern terminology, this is a typical example of +/// the \e Iterator pattern. +/// +/// The RDATA objects stored in the \c RRset are considered to form +/// a unidirectional list from the \c RdataIterator point of view (while +/// the actual implementation in the derived \c RRset may not use a list). +/// We call this unidirectional list the %rdata list. +/// +/// An \c RdataIterator object internally (and conceptually) holds a +/// %rdata cursor, which points to a specific item of the %rdata list. +/// +/// Note about design choice: as is clear from the interface, \c RdataIterator +/// is not compatible with the standard iterator classes. +/// Although it would be useful (for example, we could then use STL algorithms) +/// and is not necessarily impossible, it would make the iterator implementation +/// much more complicated. +/// For instance, any standard iterator must be assignable and +/// copy-constructible. +/// So we'd need to implement \c RdataIterator::operator=() in a polymorphic +/// way. This will require non-trivial implementation tricks. +/// We believe the simplified iterator interface as provided by the +/// \c RdataIterator class is sufficient in practice: +/// Most applications will simply go through the RDATA objects contained in +/// an RRset, examining (and possibly using) each object, as one path +/// operation. +class RdataIterator { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private to make it explicit that this is a pure base class. + //@{ +protected: + /// \brief The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + RdataIterator() {} +public: + /// \brief Destructor + virtual ~RdataIterator() {} +private: + RdataIterator(const RdataIterator& source); + RdataIterator& operator=(const RdataIterator& source); + //@} + +public: + /// \brief Move the %rdata cursor to the first RDATA in the %rdata list + /// (if any). + /// + /// This method can safely be called multiple times, even after moving + /// the %rdata cursor forward by the \c next() method. + /// + /// This method should never throw an exception. + virtual void first() = 0; + + /// \brief Move the %rdata cursor to the next RDATA in the %rdata list + /// (if any). + /// + /// This method should never throw an exception. + virtual void next() = 0; + + /// \brief Return the current \c Rdata corresponding to the %rdata cursor. + /// + /// \return A reference to an \c rdata::::Rdata object corresponding + /// to the %rdata cursor. + virtual const rdata::Rdata& getCurrent() const = 0; + + /// \brief Return true iff the %rdata cursor has reached the end of the + /// %rdata list. + /// + /// Once this method returns \c true, the behavior of any subsequent + /// call to \c next() or \c getCurrent() is undefined. + /// Likewise, the result of \c isLast() call followed by such undefined + /// operations is also undefined. + /// + /// This method should never throw an exception. + /// + /// \return \c true if the %rdata cursor has reached the end of the + /// %rdata list; otherwise \c false. + virtual bool isLast() const = 0; +}; + +/// \brief The \c BasicRRset class is a concrete derived class of +/// \c AbstractRRset that defines a straightforward RRset implementation. +/// +/// designed to be as portable as possible. performance is a secondary +/// concern for this class. +/// +/// We'd use the default implementations for the toWire() +/// variants as defined in the base class. These are not fully optimized +/// for performance, but, again, it's a secondary goal for this generic +/// class. +class BasicRRset : public AbstractRRset { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private. The intended use case wouldn't require copies of + /// a \c BasicRRset object; once created, it would normally be used + /// as a \c const object (via references). + //@{ +private: + BasicRRset(const BasicRRset& source); + BasicRRset& operator=(const BasicRRset& source); +public: + /// \brief Constructor from (mostly) fixed parameters of the RRset. + /// + /// This constructor is normally expected to be exception free, but + /// copying the name may involve resource allocation, and if it fails + /// the corresponding standard exception will be thrown. + /// + /// \param name The owner name of the RRset. + /// \param rrclass The RR class of the RRset. + /// \param rrtype The RR type of the RRset. + /// \param ttl The TTL of the RRset. + explicit BasicRRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl); + /// \brief The destructor. + virtual ~BasicRRset(); + //@} + + /// + /// \name Getter and Setter Methods + /// + //@{ + /// \brief Returns the number of \c Rdata objects contained in the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return The number of \c Rdata objects contained. + virtual unsigned int getRdataCount() const; + + /// \brief Returns the owner name of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c RRset owner name. + virtual const Name& getName() const; + + /// \brief Returns the RR Class of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c RRset. + virtual const RRClass& getClass() const; + + /// \brief Returns the RR Type of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c RRset. + virtual const RRType& getType() const; + + /// \brief Returns the TTL of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRTTL class object corresponding to the + /// TTL of the \c RRset. + virtual const RRTTL& getTTL() const; + + /// \brief Updates the owner name of the \c RRset. + /// + /// This method normally does not throw an exception, but could throw + /// some standard exception on resource allocation failure if the + /// internal copy of the \c name involves resource allocation and it + /// fails. + /// + /// \param name A reference to a \c RRTTL class object to be copied as the + /// new TTL. + virtual void setName(const Name& name); + + /// \brief Updates the TTL of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \param ttl A reference to a \c RRTTL class object to be copied as the + /// new TTL. + virtual void setTTL(const RRTTL& ttl); + //@} + + /// + /// \name Converter Methods + /// + //@{ + /// \brief Convert the RRset to a string. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toText(). + virtual std::string toText() const; + + /// \brief Render the RRset in the wire format with name compression and + /// truncation handling. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toWire(MessageRenderer&)const. + virtual unsigned int toWire(MessageRenderer& renderer) const; + + /// \brief Render the RRset in the wire format without any compression. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toWire(OutputBuffer&)const. + virtual unsigned int toWire(OutputBuffer& buffer) const; + //@} /// /// \name RDATA manipulation methods /// //@{ - virtual void addRdata(const rdata::RdataPtr rdata) = 0; - /// This method has the default implementation. - /// Note: since concrete classes would define the pure virtual version - /// of \c addRdata(), they'll need to declare the use of this method - /// to avoid name hiding (unless they redefine this method): - /// \code class CustomizedRRset : public AbstractRRset { - /// public: - /// using AbstractRRset::addRdata; - /// ... - /// }; \endcode + /// \brief Add an RDATA to the RRset (pointer version). + /// + /// This method is normally expected to be exception free, but it may + /// involve resource allocation, and if it fails the corresponding + /// standard exception will be thrown. + /// + /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added + /// to the \c BasicRRset. + virtual void addRdata(rdata::ConstRdataPtr rdata); + + /// \brief Add an RDATA to the RRset (reference version). + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::addRdata(const rdata::Rdata&). virtual void addRdata(const rdata::Rdata& rdata); - virtual RdataIteratorPtr getRdataIterator() const = 0; - //@} -}; -class RdataIterator { -public: - virtual ~RdataIterator() {} - virtual void first() = 0; - virtual void next() = 0; - virtual const rdata::Rdata& getCurrent() const = 0; - virtual bool isLast() const = 0; -}; - -/// Straightforward RRset implementation. -/// designed to be as portable as possible. performance is a secondary -/// concern for this class. -class BasicRRset : public AbstractRRset { -private: - BasicRRset(const BasicRRset& source); - void operator=(const BasicRRset& source); -public: - explicit BasicRRset(const Name& name, const RRClass& rrclass, - const RRType& rrtype, const RRTTL& ttl); - virtual ~BasicRRset(); + /// \brief Return an iterator to go through all RDATA stored in the + /// \c BasicRRset. /// - /// See the note for the base class version. + /// This is a concrete derived implementation of + /// \c AbstractRRset::getRdataIterator(). /// - using AbstractRRset::addRdata; - virtual void addRdata(const rdata::RdataPtr rdata); - /// - /// We'd use the default implementations for toText() and toWire() - /// variants as defined in the base class. These are not fully optimized - /// for performance, but, again, it's a secondary goal for this generic - /// class. - - /// - /// \name Getter and setter methods - /// - //@{ - virtual unsigned int getRdataCount() const; - virtual const Name& getName() const; - virtual const RRClass& getClass() const; - virtual const RRType& getType() const; - virtual const RRTTL& getTTL() const; - virtual void setTTL(const RRTTL& ttl); - //@} + /// This method dynamically allocates resources. If it fails it will + /// throw the corresponding standard exception. + /// The iterator methods for the \c BasicRRset class are exception free. /// + /// \return A pointer-like object pointing to the derived \c RdataIterator + /// object for the \c BasicRRset class. virtual RdataIteratorPtr getRdataIterator() const; + //@} private: BasicRRsetImpl* impl_; }; +/// \brief Insert the \c RRset as a string into stream. +/// +/// This method convert the \c rrset into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global \c operator<< to behave as described in +/// \c %ostream::%operator<< but applied to RRset objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rrset A reference to a (derived class of) \c AbstractRRset object +/// output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. std::ostream& operator<<(std::ostream& os, const AbstractRRset& rrset); } // end of namespace dns } // end of namespace isc diff --git a/src/lib/dns/cpp/rrset_unittest.cc b/src/lib/dns/cpp/rrset_unittest.cc index 9de29385ab..253ab38426 100644 --- a/src/lib/dns/cpp/rrset_unittest.cc +++ b/src/lib/dns/cpp/rrset_unittest.cc @@ -168,6 +168,17 @@ TEST_F(RRsetTest, iterator) rrset_a_empty.addRdata(in::A("192.0.2.1")); rrset_a_empty.addRdata(in::A("192.0.2.2")); addRdataTestCommon(rrset_a_empty); + + // Rewind test: should be repeat the iteration by calling first(). + for (int i = 0; i < 2; ++i) { + it = rrset_a_empty.getRdataIterator(); + it->first(); + EXPECT_FALSE(it->isLast()); + it->next(); + EXPECT_FALSE(it->isLast()); + it->next(); + EXPECT_TRUE(it->isLast()); + } } TEST_F(RRsetTest, toText) @@ -175,6 +186,9 @@ TEST_F(RRsetTest, toText) EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n" "test.example.com. 3600 IN A 192.0.2.2\n", rrset_a.toText()); + + // toText() cannot be performed for an empty RRset. + EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset); } TEST_F(RRsetTest, toWireBuffer) @@ -184,6 +198,10 @@ TEST_F(RRsetTest, toWireBuffer) UnitTestUtil::readWireData("testdata/rrset_toWire1", wiredata); EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(), buffer.getLength(), &wiredata[0], wiredata.size()); + + // toWire() cannot be performed for an empty RRset. + buffer.clear(); + EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset); } TEST_F(RRsetTest, toWireRenderer) @@ -196,6 +214,10 @@ TEST_F(RRsetTest, toWireRenderer) UnitTestUtil::readWireData("testdata/rrset_toWire2", wiredata); EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(), buffer.getLength(), &wiredata[0], wiredata.size()); + + // toWire() cannot be performed for an empty RRset. + renderer.clear(); + EXPECT_THROW(rrset_a_empty.toWire(renderer), EmptyRRset); } // test operator<<. We simply confirm it appends the result of toText(). From e421a3a89503c43d02fb3fe0c33df8c2dc265784 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Tue, 23 Feb 2010 08:04:50 +0000 Subject: [PATCH 55/81] added a test for BasicRRset::setName() git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@918 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rrset_unittest.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/dns/cpp/rrset_unittest.cc b/src/lib/dns/cpp/rrset_unittest.cc index 253ab38426..a3617e9cc0 100644 --- a/src/lib/dns/cpp/rrset_unittest.cc +++ b/src/lib/dns/cpp/rrset_unittest.cc @@ -114,6 +114,12 @@ TEST_F(RRsetTest, setTTL) EXPECT_EQ(RRTTL(0), rrset_a.getTTL()); } +TEST_F(RRsetTest, setName) +{ + rrset_a.setName(test_nsname); + EXPECT_EQ(test_nsname, rrset_a.getName()); +} + void addRdataTestCommon(const RRset& rrset) { From a3046fe921e0a149d737e95e53ac7dafbcd48f8a Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Tue, 23 Feb 2010 08:26:59 +0000 Subject: [PATCH 56/81] more documentation git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-dnsrrset@919 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rrset.h | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/dns/cpp/rrset.h b/src/lib/dns/cpp/rrset.h index fbea6fb3db..7242ffac2c 100644 --- a/src/lib/dns/cpp/rrset.h +++ b/src/lib/dns/cpp/rrset.h @@ -149,8 +149,8 @@ typedef boost::shared_ptr RdataIteratorPtr; /// "sort" and "search(find)" method? /// - what about comparing two RRsets of the same type? If we need this, /// should it compare rdata's as a set or as a list (i.e. compare -/// each %rdata one by one or as a whole)? c.f. NLnet Labs' -/// ldns +/// each %rdata one by one or as a whole)? c.f. NLnet Labs' ldns +/// (http://www.nlnetlabs.nl/projects/ldns/doc/index.html) /// has \c ldns_rr_list_compare(), which takes the latter approach /// (seemingly assuming the caller sorts the lists beforehand). /// - BIND9 libdns has some special DNSSEC-related methods @@ -498,13 +498,16 @@ public: /// \brief The \c BasicRRset class is a concrete derived class of /// \c AbstractRRset that defines a straightforward RRset implementation. /// -/// designed to be as portable as possible. performance is a secondary -/// concern for this class. +/// This class is designed to be as portable as possible, and so it adopts +/// the Pimpl idiom to hide as many details as possible. +/// Performance is a secondary concern for this class. /// -/// We'd use the default implementations for the toWire() -/// variants as defined in the base class. These are not fully optimized -/// for performance, but, again, it's a secondary goal for this generic -/// class. +/// This class is intended to be used by applications that only need +/// moderate level of performance with full functionality provided by +/// the \c AbstractRRset interfaces. +/// Highly performance-sensitive applications, such as a large scale +/// authoritative or caching name servers will implement and use a customized +/// version of derived \c AbstractRRset class. class BasicRRset : public AbstractRRset { /// /// \name Constructors and Destructor From 8a98890b6f3c6246c5a156c71feff43ea95a00c1 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 11:10:14 +0000 Subject: [PATCH 57/81] implemented getItemList and getFullConfig in cpp version (+tests) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@920 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 76 +++++++++++++-------- src/lib/config/cpp/config_data.h | 2 +- src/lib/config/cpp/config_data_unittests.cc | 16 ++++- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index 620a912700..d9fded8a70 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -26,12 +26,12 @@ using namespace isc::data; namespace isc { namespace config { -ElementPtr +static ElementPtr find_spec_part(ElementPtr spec, const std::string& identifier) { - //std::cout << "[XX] find_spec_part" << std::endl; + //std::cout << "[XX] find_spec_part for " << identifier << std::endl; if (!spec) { return ElementPtr(); } - //std::cout << "in: " << spec << std::endl; + //std::cout << "in: " << std::endl << spec << std::endl; ElementPtr spec_part = spec; if (identifier == "") { //std::cout << "[XX] empty id" << std::endl; @@ -48,7 +48,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) if (list_el->getType() == Element::map && list_el->contains("item_name") && list_el->get("item_name")->stringValue() == part) { - spec_part = list_el->get("item_name"); + spec_part = list_el; found = true; } } @@ -63,7 +63,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) if (list_el->getType() == Element::map && list_el->contains("item_name") && list_el->get("item_name")->stringValue() == part) { - spec_part = list_el->get("item_name"); + spec_part = list_el; found = true; } } @@ -73,7 +73,12 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } } - id = id.substr(sep + 1); + if (sep < id.size()) { + id = id.substr(sep + 1); + } else { + id = ""; + } + sep = id.find("/"); } if (id != "" && id != "/") { if (spec_part->getType() == Element::list) { @@ -108,9 +113,35 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } } + //std::cout << "[XX] found spec part: " << std::endl << spec_part << std::endl; return spec_part; } +static void +spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool recurse = false) +{ + if (spec_part->getType() == Element::list) { + BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) { + if (list_el->getType() == Element::map && + list_el->contains("item_name")) { + std::string new_prefix = prefix; + if (prefix != "") { new_prefix += "/"; } + new_prefix += list_el->get("item_name")->stringValue(); + if (recurse && list_el->get("item_type")->stringValue() == "map") { + spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse); + } else { + if (list_el->get("item_type")->stringValue() == "map" || + list_el->get("item_type")->stringValue() == "list" + ) { + new_prefix += "/"; + } + result->add(Element::create(new_prefix)); + } + } + } + } +} + ElementPtr ConfigData::getValue(const std::string& identifier) { @@ -137,27 +168,9 @@ ConfigData::getValue(bool& is_default, const std::string& identifier) return value; } -void -spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool recurse = false) -{ - if (spec_part->getType() == Element::list) { - BOOST_FOREACH(ElementPtr list_el, spec_part->listValue()) { - if (list_el->getType() == Element::map && - list_el->contains("item_name")) { - result->add(Element::create(prefix + "/" + list_el->get("item_name")->stringValue())); - } - } - } else if (spec_part->getType() == Element::map && - spec_part->contains("map_item_spec") - ) { - if (recurse) { - spec_name_list(result, spec_part->get("map_item_spec"), prefix + "/" + spec_part->get("item_name")->stringValue(), recurse); - } else { - result->add(Element::create(prefix + "/" + spec_part->get("item_name")->stringValue())); - } - } -} - +/// Returns an ElementPtr pointing to a ListElement containing +/// StringElements with the names of the options at the given +/// identifier. If recurse is true, maps will be expanded as well ElementPtr ConfigData::getItemList(const std::string& identifier, bool recurse) { @@ -170,10 +183,17 @@ ConfigData::getItemList(const std::string& identifier, bool recurse) return result; } +/// Returns an ElementPtr containing a MapElement with identifier->value +/// pairs. ElementPtr ConfigData::getFullConfig() { - return ElementPtr(); + ElementPtr result = Element::createFromString("{}"); + ElementPtr items = getItemList("", true); + BOOST_FOREACH(ElementPtr item, items->listValue()) { + result->set(item->stringValue(), getValue(item->stringValue())); + } + return result; } } diff --git a/src/lib/config/cpp/config_data.h b/src/lib/config/cpp/config_data.h index 5520bf15fa..b15af26b33 100644 --- a/src/lib/config/cpp/config_data.h +++ b/src/lib/config/cpp/config_data.h @@ -43,7 +43,7 @@ public: void setModuleSpec(ModuleSpec module_spec) { _module_spec = module_spec; }; void setLocalConfig(ElementPtr config) { _config = config; } ElementPtr getLocalConfig() { return _config; } - ElementPtr getItemList(const std::string& identifier, bool recurse = false); + ElementPtr getItemList(const std::string& identifier = "", bool recurse = false); ElementPtr getFullConfig(); private: diff --git a/src/lib/config/cpp/config_data_unittests.cc b/src/lib/config/cpp/config_data_unittests.cc index 90b486fa8e..f66db4bc4c 100644 --- a/src/lib/config/cpp/config_data_unittests.cc +++ b/src/lib/config/cpp/config_data_unittests.cc @@ -98,6 +98,20 @@ TEST(ConfigData, getItemList) { ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); ConfigData cd = ConfigData(spec2); - //EXPECT_EQ("", cd.getItemList("")->str()); + EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/\" ]", cd.getItemList()->str()); + EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str()); +} + +TEST(ConfigData, getFullConfig) { + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ConfigData cd = ConfigData(spec2); + + EXPECT_EQ("{\"item1\": 1, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None}", cd.getFullConfig()->str()); + ElementPtr my_config = Element::createFromString("{\"item1\": 2}"); + cd.setLocalConfig(my_config); + EXPECT_EQ("{\"item1\": 2, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None}", cd.getFullConfig()->str()); + ElementPtr my_config2 = Element::createFromString("{\"item6\": { \"value1\": \"a\" } }"); + cd.setLocalConfig(my_config2); + EXPECT_EQ("{\"item1\": 1, \"item2\": 1.1, \"item3\": True, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None}", cd.getFullConfig()->str()); } From 6af22645459aa1df5ff22f5c9432abc5684e261a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 13:03:09 +0000 Subject: [PATCH 58/81] allow for std:string exception constructors getValue now raises DataNotFoundError if a bad identifier is given some more tests git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@921 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 27 +++++++-------------- src/lib/config/cpp/config_data.h | 2 +- src/lib/config/cpp/config_data_unittests.cc | 11 +++++++++ src/lib/exceptions/cpp/exceptions.h | 10 ++++++++ 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index d9fded8a70..f869682900 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -53,8 +53,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } if (!found) { - // raise exception? - return ElementPtr(); + dns_throw(DataNotFoundError, identifier); } } else if (spec_part->getType() == Element::map) { if (spec_part->contains("map_item_spec")) { @@ -68,8 +67,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } if (!found) { - // raise exception? - return ElementPtr(); + dns_throw(DataNotFoundError, identifier); } } } @@ -92,8 +90,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } if (!found) { - // raise exception? - return ElementPtr(); + dns_throw(DataNotFoundError, identifier); } } else if (spec_part->getType() == Element::map) { if (spec_part->contains("map_item_spec")) { @@ -107,8 +104,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) } } if (!found) { - // raise exception? - return ElementPtr(); + dns_throw(DataNotFoundError, identifier); } } } @@ -153,17 +149,12 @@ ElementPtr ConfigData::getValue(bool& is_default, const std::string& identifier) { ElementPtr value = _config->find(identifier); - if (!value) { - ElementPtr spec_part = find_spec_part(_module_spec.getConfigSpec(), identifier); - if (spec_part) { - value = spec_part->get("item_default"); - is_default = true; - } else { - // we should raise an error here - dns_throw(DataNotFoundError, "identifier not found"); - } - } else { + if (value) { is_default = false; + } else { + ElementPtr spec_part = find_spec_part(_module_spec.getConfigSpec(), identifier); + value = spec_part->get("item_default"); + is_default = true; } return value; } diff --git a/src/lib/config/cpp/config_data.h b/src/lib/config/cpp/config_data.h index b15af26b33..d336bdf2c6 100644 --- a/src/lib/config/cpp/config_data.h +++ b/src/lib/config/cpp/config_data.h @@ -28,7 +28,7 @@ namespace config { class DataNotFoundError : public isc::Exception { public: - DataNotFoundError(const char* file, size_t line, const char* what) : + DataNotFoundError(const char* file, size_t line, const std::string& what) : isc::Exception(file, line, what) {} }; diff --git a/src/lib/config/cpp/config_data_unittests.cc b/src/lib/config/cpp/config_data_unittests.cc index f66db4bc4c..ee952d6de6 100644 --- a/src/lib/config/cpp/config_data_unittests.cc +++ b/src/lib/config/cpp/config_data_unittests.cc @@ -43,20 +43,31 @@ TEST(ConfigData, getValue) { //std::cout << "[XX] SPEC2: " << cd.getModuleSpec().getFullSpec() << std::endl; bool is_default; //ElementPtr value = cd.getValue(is_default, "item1"); + EXPECT_EQ(1, cd.getValue("item1")->intValue()); EXPECT_EQ(1, cd.getValue(is_default, "item1")->intValue()); EXPECT_TRUE(is_default); + EXPECT_EQ(1.1, cd.getValue("item2")->doubleValue()); EXPECT_EQ(1.1, cd.getValue(is_default, "item2")->doubleValue()); EXPECT_TRUE(is_default); + EXPECT_TRUE(cd.getValue("item3")->boolValue()); EXPECT_TRUE(cd.getValue(is_default, "item3")->boolValue()); EXPECT_TRUE(is_default); + EXPECT_EQ("test", cd.getValue("item4")->stringValue()); EXPECT_EQ("test", cd.getValue(is_default, "item4")->stringValue()); EXPECT_TRUE(is_default); + EXPECT_EQ("a", cd.getValue("item5")->get(0)->stringValue()); EXPECT_EQ("a", cd.getValue(is_default, "item5")->get(0)->stringValue()); EXPECT_TRUE(is_default); + EXPECT_EQ("b", cd.getValue("item5")->get(1)->stringValue()); EXPECT_EQ("b", cd.getValue(is_default, "item5")->get(1)->stringValue()); EXPECT_TRUE(is_default); + EXPECT_EQ("{}", cd.getValue("item6")->str()); EXPECT_EQ("{}", cd.getValue(is_default, "item6")->str()); EXPECT_TRUE(is_default); + + EXPECT_THROW(cd.getValue("no_such_item")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("item6/no_such_item")->str(), DataNotFoundError); + } TEST(ConfigData, setLocalConfig) { diff --git a/src/lib/exceptions/cpp/exceptions.h b/src/lib/exceptions/cpp/exceptions.h index 014808670b..9c496c2a00 100644 --- a/src/lib/exceptions/cpp/exceptions.h +++ b/src/lib/exceptions/cpp/exceptions.h @@ -43,6 +43,16 @@ public: /// @param what a description (type) of the exception. Exception(const char* file, size_t line, const char* what) : file_(file), line_(line), what_(what) {} + + /// \brief Constructor for a given type for exceptions with file name and + /// file line number. + /// + /// @param file the file name where the exception was thrown. + /// @param line the line in @ref file where the exception was thrown. + /// @param what a description (type) of the exception. + Exception(const char* file, size_t line, const std::string& what) : + file_(file), line_(line), what_(what) {} + /// The destructor virtual ~Exception() throw() {} //@} From c502e0e0f49622afe6e65416f4bed19cbf427634 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 13:52:05 +0000 Subject: [PATCH 59/81] doxygen git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@922 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 9 ++++-- src/lib/config/cpp/config_data.h | 51 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index f869682900..18ca75b996 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -153,8 +153,13 @@ ConfigData::getValue(bool& is_default, const std::string& identifier) is_default = false; } else { ElementPtr spec_part = find_spec_part(_module_spec.getConfigSpec(), identifier); - value = spec_part->get("item_default"); - is_default = true; + if (spec_part->contains("item_default")) { + value = spec_part->get("item_default"); + is_default = true; + } else { + is_default = false; + return ElementPtr(); + } } return value; } diff --git a/src/lib/config/cpp/config_data.h b/src/lib/config/cpp/config_data.h index d336bdf2c6..b5f24379f1 100644 --- a/src/lib/config/cpp/config_data.h +++ b/src/lib/config/cpp/config_data.h @@ -26,6 +26,9 @@ namespace isc { namespace config { +/// This exception is thrown when the caller is trying to access +/// data that doesn't exist (i.e. with an identifier that does not +/// point to anything defined in the .spec file) class DataNotFoundError : public isc::Exception { public: DataNotFoundError(const char* file, size_t line, const std::string& what) : @@ -34,16 +37,64 @@ public: class ConfigData { public: + /// Constructs a ConfigData option with no specification and an + /// empty configuration. ConfigData() { _config = Element::createFromString("{}"); }; + /// Constructs a ConfigData option with the given specification + /// and an empty configuration. + /// \param module_spec A ModuleSpec for the relevant module ConfigData(const ModuleSpec& module_spec) : _module_spec(module_spec) { _config = Element::createFromString("{}"); } + /// Returns the value currently set for the given identifier + /// If no value is set, the default value (as specified by the + /// .spec file) is returned. If there is no value and no default, + /// an empty ElementPtr is returned. + /// Raises a DataNotFoundError if the identifier is bad. + /// \param identifier The identifier pointing to the configuration + /// value that is to be returned ElementPtr getValue(const std::string& identifier); + + /// Returns the value currently set for the given identifier + /// If no value is set, the default value (as specified by the + /// .spec file) is returned. If there is no value and no default, + /// an empty ElementPtr is returned. + /// Raises a DataNotFoundError if the identifier is bad. + /// \param is_default will be set to true if the value is taken + /// from the specifications item_default setting, + /// false otherwise + /// \param identifier The identifier pointing to the configuration + /// value that is to be returned ElementPtr getValue(bool &is_default, const std::string& identifier); + + /// Returns the ModuleSpec associated with this ConfigData object const ModuleSpec getModuleSpec() { return _module_spec; }; + /// Set the ModuleSpec associated with this ConfigData object void setModuleSpec(ModuleSpec module_spec) { _module_spec = module_spec; }; + /// Set the local configuration (i.e. all non-default values) + /// \param config An ElementPtr pointing to a MapElement containing + /// *all* non-default configuration values. Existing values + /// will be removed. void setLocalConfig(ElementPtr config) { _config = config; } + /// Returns the local (i.e. non-default) configuration. + /// \returns An ElementPtr pointing to a MapElement containing all + /// non-default configuration options. ElementPtr getLocalConfig() { return _config; } + /// Returns a list of all possible configuration options as specified + /// by the ModuleSpec. + /// \param identifier If given, show the items at the given identifier + /// (iff that is also a MapElement) + /// \param recurse If true, child MapElements will be traversed to + /// add their identifiers to the result list + /// \return An ElementPtr pointing to a ListElement containing + /// StringElements that specify the identifiers at the given + /// location (or all possible identifiers if identifier=="" + /// and recurse==false) ElementPtr getItemList(const std::string& identifier = "", bool recurse = false); + /// Returns all current configuration settings (both non-default and default). + /// \return An ElementPtr pointing to a MapElement containing + /// string->value elements, where the string is the + /// full identifier of the configuration option and the + /// value is an ElementPtr with the value. ElementPtr getFullConfig(); private: From 6ac9500c3f830c4879cee0d081f08c1352831391 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 15:35:22 +0000 Subject: [PATCH 60/81] Be more clear about builtin prompt. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@923 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bindctl/bindctl.1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bin/bindctl/bindctl.1 b/src/bin/bindctl/bindctl.1 index 7183897f61..edfeb5c579 100644 --- a/src/bin/bindctl/bindctl.1 +++ b/src/bin/bindctl/bindctl.1 @@ -28,11 +28,15 @@ If this connection is not established, will exit. .\" TODO: what if msgq is running but no BindCtl or Boss groups? .Pp -The command-line prompt shows +The +.Nm +prompt shows .Dq "\*[Gt] " . The prompt will also display the location if changed. The options are based on the module in use. -The command-line usage is: +The +.Nm +usage is: .Pp .Ic module Ic command Op Ar "param1 = value1" Op Ar ", param2 = value2" .Pp From 96cfe830cb926afbfc1c77cf735140be750e724e Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 15:36:04 +0000 Subject: [PATCH 61/81] Fix typo in docs. While here minor capitalization, grammer, punctuation changes. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@924 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bindctl/cmdparse.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/bindctl/cmdparse.py b/src/bin/bindctl/cmdparse.py index 2b546f4c99..dac46699f6 100644 --- a/src/bin/bindctl/cmdparse.py +++ b/src/bin/bindctl/cmdparse.py @@ -34,10 +34,10 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str) NAME_PATTERN = re.compile("^\s*(?P[\w]+)(?P\s*)(?P.*)$") class BindCmdParse: - """ This class will parse the command line usr input into three part - module name, command, parameters - the first two parts are strings and parameter is one hash, - parameter part is optional + """ This class will parse the command line user input into three parts: + module name, command, parameters. + The first two parts are strings and parameter is one hash. + The parameter part is optional. Example: zone reload, zone_name=example.com module == zone From 33fdc92ec0070c22e3e90189f3ad0d20f2de96a2 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 15:51:03 +0000 Subject: [PATCH 62/81] 100% coverage on config_data.cc :) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@925 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 30 +++---------- src/lib/config/cpp/config_data_unittests.cc | 43 +++++++++++-------- .../config/python/isc/config/config_data.py | 2 +- .../python/isc/config/config_data_test.py | 1 + .../config/python/isc/config/module_spec.py | 4 +- src/lib/config/testdata/spec22.spec | 2 +- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index 18ca75b996..efe6457ce5 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -34,8 +34,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) //std::cout << "in: " << std::endl << spec << std::endl; ElementPtr spec_part = spec; if (identifier == "") { - //std::cout << "[XX] empty id" << std::endl; - return ElementPtr(); + dns_throw(DataNotFoundError, "Empty identifier"); } std::string id = identifier; size_t sep = id.find('/'); @@ -55,27 +54,8 @@ find_spec_part(ElementPtr spec, const std::string& identifier) if (!found) { dns_throw(DataNotFoundError, identifier); } - } else if (spec_part->getType() == Element::map) { - if (spec_part->contains("map_item_spec")) { - bool found = false; - BOOST_FOREACH(ElementPtr list_el, spec_part->get("map_item_spec")->listValue()) { - if (list_el->getType() == Element::map && - list_el->contains("item_name") && - list_el->get("item_name")->stringValue() == part) { - spec_part = list_el; - found = true; - } - } - if (!found) { - dns_throw(DataNotFoundError, identifier); - } - } - } - if (sep < id.size()) { - id = id.substr(sep + 1); - } else { - id = ""; } + id = id.substr(sep + 1); sep = id.find("/"); } if (id != "" && id != "/") { @@ -106,6 +86,8 @@ find_spec_part(ElementPtr spec, const std::string& identifier) if (!found) { dns_throw(DataNotFoundError, identifier); } + } else { + dns_throw(DataNotFoundError, identifier); } } } @@ -135,6 +117,8 @@ spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool } } } + } else if (spec_part->getType() == Element::map && spec_part->contains("map_item_spec")) { + spec_name_list(result, spec_part->get("map_item_spec"), prefix, recurse); } } @@ -158,7 +142,7 @@ ConfigData::getValue(bool& is_default, const std::string& identifier) is_default = true; } else { is_default = false; - return ElementPtr(); + value = ElementPtr(); } } return value; diff --git a/src/lib/config/cpp/config_data_unittests.cc b/src/lib/config/cpp/config_data_unittests.cc index ee952d6de6..a795fb1b3c 100644 --- a/src/lib/config/cpp/config_data_unittests.cc +++ b/src/lib/config/cpp/config_data_unittests.cc @@ -28,7 +28,7 @@ using namespace isc::config; ConfigData setupSpec2() { - ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec"); return ConfigData(spec2); } @@ -38,36 +38,42 @@ TEST(ConfigData, Creation) { } TEST(ConfigData, getValue) { - ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec"); + ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec"); ConfigData cd = ConfigData(spec2); //std::cout << "[XX] SPEC2: " << cd.getModuleSpec().getFullSpec() << std::endl; bool is_default; //ElementPtr value = cd.getValue(is_default, "item1"); - EXPECT_EQ(1, cd.getValue("item1")->intValue()); - EXPECT_EQ(1, cd.getValue(is_default, "item1")->intValue()); + EXPECT_EQ(9, cd.getValue("value1")->intValue()); + EXPECT_EQ(9, cd.getValue(is_default, "value1")->intValue()); EXPECT_TRUE(is_default); - EXPECT_EQ(1.1, cd.getValue("item2")->doubleValue()); - EXPECT_EQ(1.1, cd.getValue(is_default, "item2")->doubleValue()); + EXPECT_EQ(9.9, cd.getValue("value2")->doubleValue()); + EXPECT_EQ(9.9, cd.getValue(is_default, "value2")->doubleValue()); EXPECT_TRUE(is_default); - EXPECT_TRUE(cd.getValue("item3")->boolValue()); - EXPECT_TRUE(cd.getValue(is_default, "item3")->boolValue()); + EXPECT_FALSE(cd.getValue("value3")->boolValue()); + EXPECT_FALSE(cd.getValue(is_default, "value3")->boolValue()); EXPECT_TRUE(is_default); - EXPECT_EQ("test", cd.getValue("item4")->stringValue()); - EXPECT_EQ("test", cd.getValue(is_default, "item4")->stringValue()); + EXPECT_EQ("default_string", cd.getValue("value4")->stringValue()); + EXPECT_EQ("default_string", cd.getValue(is_default, "value4")->stringValue()); EXPECT_TRUE(is_default); - EXPECT_EQ("a", cd.getValue("item5")->get(0)->stringValue()); - EXPECT_EQ("a", cd.getValue(is_default, "item5")->get(0)->stringValue()); + EXPECT_EQ("a", cd.getValue("value5")->get(0)->stringValue()); + EXPECT_EQ("a", cd.getValue(is_default, "value5")->get(0)->stringValue()); EXPECT_TRUE(is_default); - EXPECT_EQ("b", cd.getValue("item5")->get(1)->stringValue()); - EXPECT_EQ("b", cd.getValue(is_default, "item5")->get(1)->stringValue()); + EXPECT_EQ("b", cd.getValue("value5")->get(1)->stringValue()); + EXPECT_EQ("b", cd.getValue(is_default, "value5")->get(1)->stringValue()); + EXPECT_EQ("b", cd.getValue(is_default, "value5/")->get(1)->stringValue()); EXPECT_TRUE(is_default); - EXPECT_EQ("{}", cd.getValue("item6")->str()); - EXPECT_EQ("{}", cd.getValue(is_default, "item6")->str()); + EXPECT_EQ("{}", cd.getValue("value6")->str()); + EXPECT_EQ("{}", cd.getValue(is_default, "value6")->str()); + EXPECT_EQ("{}", cd.getValue(is_default, "value6/")->str()); EXPECT_TRUE(is_default); + EXPECT_EQ("[ ]", cd.getValue("value8")->str()); + EXPECT_THROW(cd.getValue("")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("/")->str(), DataNotFoundError); EXPECT_THROW(cd.getValue("no_such_item")->str(), DataNotFoundError); - EXPECT_THROW(cd.getValue("item6/no_such_item")->str(), DataNotFoundError); - + EXPECT_THROW(cd.getValue("value6/a")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("value6/no_such_item")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError); } TEST(ConfigData, setLocalConfig) { @@ -111,6 +117,7 @@ TEST(ConfigData, getItemList) { EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/\" ]", cd.getItemList()->str()); EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str()); + EXPECT_EQ("[ \"item6/value1\", \"item6/value2\" ]", cd.getItemList("item6")->str()); } TEST(ConfigData, getFullConfig) { diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 9270f499d1..73e0828567 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -195,7 +195,7 @@ class MultiConfigData: def set_specification(self, spec): """Add or update a ModuleSpec""" if type(spec) != isc.config.ModuleSpec: - raise Exception("not a datadef") + raise Exception("not a datadef: " + str(type(spec))) self._specifications[spec.get_module_name()] = spec def get_module_spec(self, module): diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 19a8e5a879..6b95a86a3e 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -29,6 +29,7 @@ class TestConfigData(unittest.TestCase): else: self.data_path = "../../../testdata" spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") + print("SPEC: " + str(type(spec))) self.cd = ConfigData(spec) #def test_module_spec_from_file(self): diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index e9b2cf1a7a..b0c4ff9530 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -50,7 +50,9 @@ def module_spec_from_file(spec_file, check = True): if 'module_spec' not in module_spec: raise ModuleSpecError("Data definition has no module_spec element") - return ModuleSpec(module_spec['module_spec'], check) + result = ModuleSpec(module_spec['module_spec'], check) + print("RETURNING: " + str(type(result))) + return result class ModuleSpec: def __init__(self, module_spec, check = True): diff --git a/src/lib/config/testdata/spec22.spec b/src/lib/config/testdata/spec22.spec index 08c2798cb5..5e9cc5654d 100644 --- a/src/lib/config/testdata/spec22.spec +++ b/src/lib/config/testdata/spec22.spec @@ -25,7 +25,7 @@ { "item_name": "value5", "item_type": "list", "item_optional": False, - "item_default": [ ], + "item_default": [ "a", "b" ], "list_item_spec": { "item_name": "list_element", "item_type": "integer", From 008ae4d3551a7cb762e5565adc29c9e10d456bcd Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 16:06:20 +0000 Subject: [PATCH 63/81] "There should be at most one statement per line." per BIND 9 coding guidelines. (I did this for helping with code coverage reporting, but I understand this file already has 100% coverage.) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@928 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index efe6457ce5..8306de27a4 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -30,7 +30,9 @@ static ElementPtr find_spec_part(ElementPtr spec, const std::string& identifier) { //std::cout << "[XX] find_spec_part for " << identifier << std::endl; - if (!spec) { return ElementPtr(); } + if (!spec) { + return ElementPtr(); + } //std::cout << "in: " << std::endl << spec << std::endl; ElementPtr spec_part = spec; if (identifier == "") { @@ -103,7 +105,9 @@ spec_name_list(ElementPtr result, ElementPtr spec_part, std::string prefix, bool if (list_el->getType() == Element::map && list_el->contains("item_name")) { std::string new_prefix = prefix; - if (prefix != "") { new_prefix += "/"; } + if (prefix != "") { + new_prefix += "/"; + } new_prefix += list_el->get("item_name")->stringValue(); if (recurse && list_el->get("item_type")->stringValue() == "map") { spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse); From 9964f279b0e8c8530e79390d2a9dd786d5dfda12 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 16:12:56 +0000 Subject: [PATCH 64/81] ok that should actually be an exception (also added test for that) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@929 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/config_data.cc | 2 +- src/lib/config/cpp/config_data_unittests.cc | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib/config/cpp/config_data.cc b/src/lib/config/cpp/config_data.cc index 8306de27a4..92cc303dbe 100644 --- a/src/lib/config/cpp/config_data.cc +++ b/src/lib/config/cpp/config_data.cc @@ -31,7 +31,7 @@ find_spec_part(ElementPtr spec, const std::string& identifier) { //std::cout << "[XX] find_spec_part for " << identifier << std::endl; if (!spec) { - return ElementPtr(); + dns_throw(DataNotFoundError, "Empty specification"); } //std::cout << "in: " << std::endl << spec << std::endl; ElementPtr spec_part = spec; diff --git a/src/lib/config/cpp/config_data_unittests.cc b/src/lib/config/cpp/config_data_unittests.cc index a795fb1b3c..1cb1a50fe1 100644 --- a/src/lib/config/cpp/config_data_unittests.cc +++ b/src/lib/config/cpp/config_data_unittests.cc @@ -38,8 +38,8 @@ TEST(ConfigData, Creation) { } TEST(ConfigData, getValue) { - ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec"); - ConfigData cd = ConfigData(spec2); + ModuleSpec spec22 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec22.spec"); + ConfigData cd = ConfigData(spec22); //std::cout << "[XX] SPEC2: " << cd.getModuleSpec().getFullSpec() << std::endl; bool is_default; //ElementPtr value = cd.getValue(is_default, "item1"); @@ -74,6 +74,12 @@ TEST(ConfigData, getValue) { EXPECT_THROW(cd.getValue("value6/a")->str(), DataNotFoundError); EXPECT_THROW(cd.getValue("value6/no_such_item")->str(), DataNotFoundError); EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError); + EXPECT_THROW(cd.getValue("value8/a")->str(), DataNotFoundError); + + ModuleSpec spec1 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec1.spec"); + ConfigData cd1 = ConfigData(spec1); + EXPECT_THROW(cd1.getValue("anything")->str(), DataNotFoundError); } TEST(ConfigData, setLocalConfig) { From aefefeb87620592949802c1dfd53cae70b4e4349 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 16:20:38 +0000 Subject: [PATCH 65/81] Add some more details and some cleanup for this manual. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@930 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/msgq/msgq.8 | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/bin/msgq/msgq.8 b/src/bin/msgq/msgq.8 index 4ee40795d5..022105c09d 100644 --- a/src/bin/msgq/msgq.8 +++ b/src/bin/msgq/msgq.8 @@ -4,7 +4,7 @@ .Os .Sh NAME .Nm msgq -.Nd message routing daemon for the Command channel +.Nd message routing daemon for the Command Channel .\" TODO: spell out CC .Sh SYNOPSIS .Nm @@ -16,9 +16,36 @@ .Sh DESCRIPTION The .Nm -daemon handles message routing for the Command channel. -It listens on 127.0.0.1. -.\" TODO: point to Command channel specification or document some here +daemon handles message routing for the Command Channel. +.Pp +The Command Channel is a message bus and subscription manager. +Programs may subscribe to certain groups to receive messages +for that group. +Every new connection to the +.Nm +receives a unique identifier -- this is the local name. +The commands it handles are: +.Bl -tag -compact -offset indent +.It getlname +receive local name. +.It send +send a message to defined subscribers. +.It subscribe +add a subscription. This means it is a listener for messages +for a specific group. +.It unsubscribe +remove a subscription. +.El +.Pp +.Nm +listens on 127.0.0.1. +.Pp +The +.Nm +daemon may be cleanly stopped by sending the +.Dv SIGTERM +signal to the process. +This shutdown does not notify the subscribers. .Sh OPTIONS The arguments are as follows: . @@ -36,15 +63,17 @@ Display more about what is doing. .El .\" .Sh SEE ALSO +.\" TODO: point to Command channel specification or document some here. .\" .Sh STANDARDS .Sh HISTORY The python version of .Nm was first coded in December 2009. -An older C version with different wire format was coded in September 2009. +The C version with now deprecated wire format was coded in September +2009. .Sh AUTHORS The .Nm -daemon and Control channel specification +daemon and Control Channel specification were initially designed by Michael Graff of ISC. .\" .Sh BUGS From 6a38cc8fbaae73822a63ac986e410d0c4a39e388 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Tue, 23 Feb 2010 16:28:39 +0000 Subject: [PATCH 66/81] added some tests for module_spec git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@931 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/module_spec_unittests.cc | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib/config/cpp/module_spec_unittests.cc b/src/lib/config/cpp/module_spec_unittests.cc index 273e25c88f..23d9d1d6f6 100644 --- a/src/lib/config/cpp/module_spec_unittests.cc +++ b/src/lib/config/cpp/module_spec_unittests.cc @@ -57,11 +57,21 @@ TEST(ModuleSpec, ReadingSpecfiles) { specfile("doesnotexist"), ": No such file or directory"); + dd = moduleSpecFromFile(specfile("spec2.spec")); + EXPECT_EQ("[ {\"command_args\": [ {\"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": False, \"item_type\": \"string\"} ], \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\"}, {\"command_args\": [ ], \"command_description\": \"Shut down BIND 10\", \"command_name\": \"shutdown\"} ]", dd.getCommandsSpec()->str()); + EXPECT_EQ("Spec2", dd.getModuleName()); + std::ifstream file; file.open(specfile("spec1.spec").c_str()); dd = moduleSpecFromFile(file); EXPECT_EQ(dd.getFullSpec()->get("module_name") ->stringValue(), "Spec1"); + EXPECT_EQ(dd.getCommandsSpec(), ElementPtr()); + + std::ifstream file2; + file2.open(specfile("spec8.spec").c_str()); + EXPECT_THROW(moduleSpecFromFile(file2), ModuleSpecError); + } TEST(ModuleSpec, SpecfileItems) { @@ -129,6 +139,18 @@ data_test(ModuleSpec dd, const std::string& data_file_name) return dd.validate_config(data); } +bool +data_test_with_errors(ModuleSpec dd, const std::string& data_file_name, ElementPtr errors) +{ + std::ifstream data_file; + + data_file.open(specfile(data_file_name).c_str()); + ElementPtr data = Element::createFromString(data_file, data_file_name); + data_file.close(); + + return dd.validate_config(data, true, errors); +} + TEST(ModuleSpec, DataValidation) { ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec")); @@ -140,4 +162,8 @@ TEST(ModuleSpec, DataValidation) { EXPECT_TRUE(data_test(dd, "data22_6.data")); EXPECT_TRUE(data_test(dd, "data22_7.data")); EXPECT_FALSE(data_test(dd, "data22_8.data")); + + ElementPtr errors = Element::createFromString("[]"); + EXPECT_FALSE(data_test_with_errors(dd, "data22_8.data", errors)); + EXPECT_EQ("[ \"Type mismatch\" ]", errors->str()); } From 4d09a1dd9a32312ab31ed4038c88993135829ff0 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 19:29:48 +0000 Subject: [PATCH 67/81] Install config_data.py and module_spec.py instead of renamed datadefinition.py. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@936 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/config/python/isc/config/Makefile.am b/src/lib/config/python/isc/config/Makefile.am index 513cdcc467..ae4f5a0cec 100644 --- a/src/lib/config/python/isc/config/Makefile.am +++ b/src/lib/config/python/isc/config/Makefile.am @@ -1,4 +1,4 @@ -PY_MODULES= __init__.py ccsession.py datadefinition.py cfgmgr.py +PY_MODULES= __init__.py ccsession.py cfgmgr.py config_data.py module_spec.py install-data-local: $(mkinstalldirs) $(DESTDIR)$(pyexecdir)/isc/config From 83ac742f491923b5a71b618a5e1d933af9d75030 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Tue, 23 Feb 2010 19:37:59 +0000 Subject: [PATCH 68/81] Remove bind-cfgd from the path. Doesn't exist. Tests still fail for me. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@937 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/bind10/bind10_test.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/bind10/bind10_test.in b/src/bin/bind10/bind10_test.in index 2f9bbb6fb2..2483c54fd1 100755 --- a/src/bin/bind10/bind10_test.in +++ b/src/bin/bind10/bind10_test.in @@ -5,7 +5,7 @@ export PYTHON_EXEC BIND10_PATH=@abs_top_srcdir@/src/bin/bind10 -PATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/bin/auth:@abs_top_srcdir@/src/bin/bind-cfgd:$PATH +PATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/bin/auth:$PATH export PATH PYTHONPATH=@abs_top_srcdir@/src/lib/cc/python:${abs_top_src_dir}/lib/cc/python/ISC From 2e0cbf8224856a102cfcf58199ab94360853a36a Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 24 Feb 2010 02:38:40 +0000 Subject: [PATCH 69/81] completed Question class with doc and more test cases git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@943 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/question.cc | 8 - src/lib/dns/cpp/question.h | 204 +++++++++++++++++++-- src/lib/dns/cpp/question_unittest.cc | 85 +++++++-- src/lib/dns/cpp/testdata/question_fromWire | 33 ++++ src/lib/dns/cpp/testdata/question_toWire1 | 14 ++ src/lib/dns/cpp/testdata/question_toWire2 | 14 ++ 6 files changed, 323 insertions(+), 35 deletions(-) create mode 100644 src/lib/dns/cpp/testdata/question_fromWire create mode 100644 src/lib/dns/cpp/testdata/question_toWire1 create mode 100644 src/lib/dns/cpp/testdata/question_toWire2 diff --git a/src/lib/dns/cpp/question.cc b/src/lib/dns/cpp/question.cc index 6e8fc0a13e..02c8802a01 100644 --- a/src/lib/dns/cpp/question.cc +++ b/src/lib/dns/cpp/question.cc @@ -25,7 +25,6 @@ #include "rrtype.h" using namespace std; -using namespace isc::dns; namespace isc { namespace dns { @@ -69,13 +68,6 @@ Question::toWire(MessageRenderer& renderer) const return (1); // number of "entries" } -bool -Question::operator==(const Question& other) const -{ - return (name_ == other.name_ && rrtype_ == other.rrtype_ && - rrclass_ == other.rrclass_); -} - ostream& operator<<(std::ostream& os, const Question& question) { diff --git a/src/lib/dns/cpp/question.h b/src/lib/dns/cpp/question.h index 23906c4701..20b5497e6b 100644 --- a/src/lib/dns/cpp/question.h +++ b/src/lib/dns/cpp/question.h @@ -18,6 +18,7 @@ #define __QUESTION_H 1 #include +#include #include @@ -29,46 +30,225 @@ namespace isc { namespace dns { class InputBuffer; +class MessageRenderer; class Question; + +/// \brief A pointer-like type pointing to an \c Question object. typedef boost::shared_ptr QuestionPtr; +/// \brief A pointer-like type pointing to an (immutable) \c Question object. +typedef boost::shared_ptr ConstQuestionPtr; + +/// \brief The \c Question class encapsulates the common search key of DNS +/// lookup, consisting of owner name, RR type and RR class. /// -/// This is a straightforward implementation of a "DNS question", modeling -/// an entry of the question section of DNS messages. -/// This could also be used as an input parameter for lookups into internal -/// data sources. +/// The primarily intended use case of this class is an entry of the question +/// section of DNS messages. +/// This could also be used as a general purpose lookup key, e.g., in a +/// custom implementation of DNS database. /// -/// Open issue: we may want to introduce an abstract base class so that we -/// can have multiple concrete implementations (e.g. for better performance) -/// of the base class in a polymorphic way. +/// In this initial implementation, the \c Question class is defined as +/// a concrete class; it's not expected to be inherited by +/// a user-defined customized class. +/// It may be worth noting that it's different from the design of the +/// RRset classes (\c AbstractRRset and its derived classes). +/// The RRset classes form an inheritance hierarchy from the base abstract +/// class. +/// This may look odd in that an "RRset" and "Question" are similar from the +/// protocol point of view: Both are used as a semantics unit of DNS messages; +/// both share the same set of components, name, RR type and RR class. +/// +/// In fact, BIND9 didn't introduce a separate data structure for Questions, +/// and use the same \c "rdataset" structure for both RRsets and Questions. +/// We could take the same approach, but chose to adopt the different design. +/// One reason for that is because a Question and an RRset are still +/// different, and a Question might not be cleanly defined if (e.g.) it were +/// a derived class of some "RRset-like" class. +/// For example, we couldn't give a reasonable semantics for \c %getTTL() or +/// \c %setTTL() methods for a Question, since it's not associated with the +/// TTL. +/// In fact, the BIND9 implementation ended up often separating the case where +/// a \c "rdataset" is from the Question section of a DNS message and the +/// case where it comes from other sections. +/// If we cannot treat them completely transparently anyway, separating the +/// class (type) would make more sense because we can exploit compilation +/// time type checks. +/// +/// On the other hand, we do not expect a strong need for customizing the +/// \c Question class, unlike the RRset. +/// Handling the Question section of a DNS message is relatively a +/// simple work comparing to RRset-involved operations, so a unified +/// straightforward implementation should suffice for any use cases +/// including performance sensitive ones. +/// +/// We may, however, still want to have customized version of Question +/// for, e.g, highly optimized behavior, and may revisit this design choice +/// as we have more experiences with this implementation. +/// +/// One disadvantage of defining RRsets and Questions as unrelated classes +/// is that we cannot handle them in a polymorphic way. +/// For example, we might want to iterate over DNS message sections and +/// render the data in the wire format, whether it's an RRset or a Question. +/// If a \c Question were a derived class of some common RRset-like class, +/// we could do this by calling rrset_or_question->%toWire(). +/// But the actual design doesn't allow this approach, which may require +/// duplicate code for almost the same operation. +/// To mitigate this problem, we intentionally used the same names +/// with the same signature for some common methods of \c Question and +/// \c AbstractRRset such as \c %getName() or \c %toWire(). +/// So the user class may use a template function that is applicable to both +/// \c Question and \c RRset to avoid writing duplicate code logic. class Question { + /// + /// \name Constructors and Destructor + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + /// + /// The default constructor is hidden as a result of defining the other + /// constructors. This is intentional; we don't want to allow a + /// \c Question object to be constructed with an invalid state. + //@{ public: + /// \brief Constructor from wire-format data. + /// + /// It simply constructs a set of \c Name, \c RRType, and \c RRClass + /// object from the \c buffer in this order, and constructs a + /// \c Question object in a straightforward way. + /// + /// It may throw an exception if the construction of these component + /// classes fails. + /// + /// \param buffer A buffer storing the wire format data. Question(InputBuffer& buffer); + + /// \brief Constructor from fixed parameters of the \c Question. + /// + /// This constructor is basically expected to be exception free, but + /// copying the name may involve resource allocation, and if it fails + /// the corresponding standard exception will be thrown. + /// + /// \param name The owner name of the \c Question. + /// \param rrclass The RR class of the \c Question. + /// \param rrtype The RR type of the \c Question. Question(const Name& name, const RRClass& rrclass, const RRType& rrtype) : name_(name), rrtype_(rrtype), rrclass_(rrclass) {} + //@} /// - /// \name Getter methods + /// \name Getter Methods + /// //@{ + /// \brief Returns the owner name of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c Question owner name. const Name& getName() const { return (name_); } + + /// \brief Returns the RR Class of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c Question. const RRType& getType() const { return (rrtype_); } + + /// \brief Returns the RR Type of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c Question. const RRClass& getClass() const { return (rrclass_); } //@} + /// + /// \name Converter Methods + /// + //@{ + /// \brief Convert the Question to a string. + /// + /// Unlike other similar methods of this library, this method terminates + /// the resulting string with a trailing newline character + /// (following the BIND9 convention). + /// + /// This method simply calls the \c %toText() methods of the corresponding + /// \c Name, \c RRType and \c RRClass classes for this \c Question, and + /// these methods may throw an exception. + /// In particular, if resource allocation fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of the \c Question. std::string toText() const; - unsigned int toWire(OutputBuffer& buffer) const; + + /// \brief Render the Question in the wire format with name compression. + /// + /// This method simply calls the \c %toWire() methods of the corresponding + /// \c Name, \c RRType and \c RRClass classes for this \c Question, and + /// these methods may throw an exception. + /// In particular, if resource allocation fails, a corresponding standard + /// exception will be thrown. + /// + /// This method returns 1, which is the number of "questions" contained + /// in the \c Question. + /// This is a meaningless value, but is provided to be consistent with + /// the corresponding method of \c AbstractRRset (see the detailed + /// class description). + /// + /// The owner name will be compressed if possible, although it's an + /// unlikely event in practice because the %Question section a DNS + /// message normally doesn't contain multiple question entries and + /// it's located right after the Header section. + /// Nevertheless, \c renderer records the information of the owner name + /// so that it can be pointed by other RRs in other sections (which is + /// more likely to happen). + /// + /// In theory, an attempt to render a Question may cause truncation + /// (when the Question section contains a large number of entries), + /// but this implementation doesn't catch that situation. + /// It would make the code unnecessarily complicated (though perhaps + /// slightly) for almost impossible case in practice. + /// An upper layer will handle the pathological case as a general error. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \return 1 unsigned int toWire(MessageRenderer& renderer) const; - bool operator==(const Question& other) const; - bool operator!=(const Question& other) const - { return !(*this == other); } + /// \brief Render the Question in the wire format without name compression. + /// + /// This method behaves like the render version except it doesn't compress + /// the owner name. + /// See \c toWire(MessageRenderer& renderer)const. + /// + /// \param buffer An output buffer to store the wire data. + /// \return 1 + unsigned int toWire(OutputBuffer& buffer) const; + //@} + private: Name name_; RRType rrtype_; RRClass rrclass_; }; +/// \brief Insert the \c Question as a string into stream. +/// +/// This method convert the \c question into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global \c operator<< to behave as described in +/// \c %ostream::%operator<< but applied to Question objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param question A reference to a \c Question object output by the +/// operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. std::ostream& operator<<(std::ostream& os, const Question& question); } // end of namespace dns } // end of namespace isc diff --git a/src/lib/dns/cpp/question_unittest.cc b/src/lib/dns/cpp/question_unittest.cc index 0740cd38af..96084d4f72 100644 --- a/src/lib/dns/cpp/question_unittest.cc +++ b/src/lib/dns/cpp/question_unittest.cc @@ -14,6 +14,11 @@ // $Id$ +#include +#include + +#include + #include "buffer.h" #include "messagerenderer.h" #include "name.h" @@ -33,36 +38,86 @@ namespace { class QuestionTest : public ::testing::Test { protected: QuestionTest() : obuffer(0), renderer(obuffer), - test_question(Name("example.com"), RRClass::IN(), - RRType::NS()) + example_name1(Name("foo.example.com")), + example_name2(Name("bar.example.com")), + test_question1(example_name1, RRClass::IN(), + RRType::NS()), + test_question2(example_name2, RRClass::CH(), + RRType::A()) {} OutputBuffer obuffer; MessageRenderer renderer; - Question test_question; - static const uint8_t wiredata[]; + Name example_name1; + Name example_name2; + Question test_question1; + Question test_question2; + vector wiredata; }; -// wire-format representation of "example.com. NS IN" -const uint8_t QuestionTest::wiredata[] = { 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, - 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, - 0x00, 0x00, 0x02, 0x00, 0x01 }; +Question +questionFromWire(const char* datafile, size_t position = 0) +{ + vector data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + buffer.setPosition(position); + + return (Question(buffer)); +} TEST_F(QuestionTest, fromWire) { - InputBuffer ibuffer(wiredata, sizeof(wiredata)); - Question q(ibuffer); - EXPECT_EQ(test_question, q); + Question q = questionFromWire("testdata/question_fromWire"); + + EXPECT_EQ(example_name1, q.getName()); + EXPECT_EQ(RRClass::IN(), q.getClass()); + EXPECT_EQ(RRType::NS(), q.getType()); + + // owner name of the second Question is compressed. It's uncommon + // (to have multiple questions), but isn't prohibited by the protocol. + q = questionFromWire("testdata/question_fromWire", 21); + EXPECT_EQ(example_name2, q.getName()); + EXPECT_EQ(RRClass::CH(), q.getClass()); + EXPECT_EQ(RRType::A(), q.getType()); + + // Pathological cases: Corresponding exceptions will be thrown from + // the underlying parser. + EXPECT_THROW(questionFromWire("testdata/question_fromWire", 31), + BadPointer); + EXPECT_THROW(questionFromWire("testdata/question_fromWire", 36), + IncompleteRRClass); } TEST_F(QuestionTest, toText) { - EXPECT_EQ("example.com. IN NS\n", test_question.toText()); + EXPECT_EQ("foo.example.com. IN NS\n", test_question1.toText()); + EXPECT_EQ("bar.example.com. CH A\n", test_question2.toText()); } -TEST_F(QuestionTest, toWire) +TEST_F(QuestionTest, toWireBuffer) { - test_question.toWire(obuffer); + test_question1.toWire(obuffer); + test_question2.toWire(obuffer); + UnitTestUtil::readWireData("testdata/question_toWire1", wiredata); EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(), - obuffer.getLength(), wiredata, sizeof(wiredata)); + obuffer.getLength(), &wiredata[0], wiredata.size()); +} + +TEST_F(QuestionTest, toWireRenderer) +{ + test_question1.toWire(renderer); + test_question2.toWire(renderer); + UnitTestUtil::readWireData("testdata/question_toWire2", wiredata); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(), + obuffer.getLength(), &wiredata[0], wiredata.size()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(QuestionTest, LeftShiftOperator) +{ + ostringstream oss; + oss << test_question1; + EXPECT_EQ(test_question1.toText(), oss.str()); } } diff --git a/src/lib/dns/cpp/testdata/question_fromWire b/src/lib/dns/cpp/testdata/question_fromWire new file mode 100644 index 0000000000..cbc28c0139 --- /dev/null +++ b/src/lib/dns/cpp/testdata/question_fromWire @@ -0,0 +1,33 @@ +# +# Wire-format data of a sequence of DNS questions. +# foo.example.com. IN NS +# bar.example.com. CH A (owner name is compressed) +# and some pathological cases +# +# 0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 (-th byte) +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#7 8 9 20 +# type/class: NS = 2, IN = 1 +00 02 00 01 + +# 1 2 3 4 5 6 +#(3) b a r [ptr=0x04] + 03 62 61 72 c0 04 +#7 8 9 30 +# type/class: A = 1, CH = 3 +00 01 00 03 + +# owner name is broken +#1 +# invalid label type +ff +#2 3 4 5 +#type/class IN/A +00 01 00 01 + +# short buffer +# (root name) +00 +#class is missing +00 01 diff --git a/src/lib/dns/cpp/testdata/question_toWire1 b/src/lib/dns/cpp/testdata/question_toWire1 new file mode 100644 index 0000000000..77886dba08 --- /dev/null +++ b/src/lib/dns/cpp/testdata/question_toWire1 @@ -0,0 +1,14 @@ +# +# Rendering two DNS Questions without name compression +# foo.example.com. IN NS +# bar.example.com. CH A +# +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: NS = 2, IN = 1 +00 02 00 01 +#(3) b a r (7) e x a m p l e (3) c o m . + 03 62 61 72 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#7 8 9 30 +# type/class: A = 1, CH = 3 +00 01 00 03 diff --git a/src/lib/dns/cpp/testdata/question_toWire2 b/src/lib/dns/cpp/testdata/question_toWire2 new file mode 100644 index 0000000000..9117ab2492 --- /dev/null +++ b/src/lib/dns/cpp/testdata/question_toWire2 @@ -0,0 +1,14 @@ +# +# Rendering two DNS Questions with name compression +# foo.example.com. IN NS +# bar.example.com. CH A +# +# 0 1 2 3 4 ... (-th byte) +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: NS = 2, IN = 1 +00 02 00 01 +#(3) b a r [ptr=0x04] + 03 62 61 72 c0 04 +# type/class: A = 1, CH = 3 +00 01 00 03 From fa51470da3fb38a470a739104a3670a729aeed9a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Wed, 24 Feb 2010 09:32:35 +0000 Subject: [PATCH 70/81] print the unexpected answer (we need a verbosity flag for modules too btw) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@947 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/cmdctl/b10-cmdctl.py.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/cmdctl/b10-cmdctl.py.in b/src/bin/cmdctl/b10-cmdctl.py.in index 6b9b001b40..d9c576c164 100644 --- a/src/bin/cmdctl/b10-cmdctl.py.in +++ b/src/bin/cmdctl/b10-cmdctl.py.in @@ -241,6 +241,7 @@ class CommandControl(): return {} else: print("Error: unexpected answer from %s" % module_name) + print(answer) except Exception as e: print(e) print('b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name)) From e84b2e3e893bf3fb858440ee7f19c9b9df7846d6 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Wed, 24 Feb 2010 14:44:51 +0000 Subject: [PATCH 71/81] function to create and 'parse' ccsession commands, and a list of fixed (non-module) command names git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@948 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index 25a9d79222..cc881e6fe7 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -74,6 +74,37 @@ def create_answer(rcode, arg = None): else: return { 'result': [ rcode ] } +# 'fixed' commands +"""Fixed names for command and configuration messages""" +COMMAND_COMMANDS_UPDATE = "commands_update" +COMMAND_SPECIFICATION_UPDATE = "specification_update" + +COMMAND_GET_COMMANDS_SPEC = "get_commands_spec" +COMMAND_GET_CONFIG = "get_config" +COMMAND_SET_CONFIG = "set_config" +COMMAND_GET_MODULE_SPEC = "get_module_spec" + +def parse_command(msg): + """Parses what may be a command message. If it looks like one, + the function returns (command, value) where command is a + string. If it is not, this function returns None, None""" + if type(msg) == dict and len(msg.items()) == 1: + cmd, value = msg.popitem() + if type(cmd) == str: + return cmd, value + return None, None + +def create_command(command_name, params = None): + """Creates a module command message with the given command name (as + specified in the module's specification, and an optional params + object""" + # TODO: validate_command with spec + cmd = [ command_name ] + if params: + cmd.append(params) + msg = { 'command': cmd } + return msg + class ModuleCCSession(ConfigData): """This class maintains a connection to the command channel, as well as configuration options for modules. The module provides From 7d5cb71991c460d820350b879621eb29716ca29e Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Wed, 24 Feb 2010 14:45:56 +0000 Subject: [PATCH 72/81] In output say b10-cfgmgr instead of bind-cfgd (catch up with name change). git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@949 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/cfgmgr/b10-cfgmgr.py.in | 2 +- src/lib/config/python/isc/config/cfgmgr.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in index f1e77b258f..8d424b0c8c 100644 --- a/src/bin/cfgmgr/b10-cfgmgr.py.in +++ b/src/bin/cfgmgr/b10-cfgmgr.py.in @@ -31,7 +31,7 @@ if __name__ == "__main__": cm.notify_boss() cm.run() except isc.cc.SessionError as se: - print("[bind-cfgd] Error creating config manager, " + print("[b10-cfgmgr] Error creating config manager, " "is the command channel daemon running?") except KeyboardInterrupt as kie: print("Got ctrl-c, exit") diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index c4425cd5cc..d918dff999 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -70,7 +70,7 @@ class ConfigManagerData: config.data = file_config else: # We can put in a migration path here for old data - raise ConfigManagerDataReadError("[bind-cfgd] Old version of data found") + raise ConfigManagerDataReadError("[b10-cfgmgr] Old version of data found") file.close() except IOError as ioe: raise ConfigManagerDataEmpty("No config file found") From cb211a31b798cb626e08b9a924c4d8622b4308b8 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 24 Feb 2010 20:26:29 +0000 Subject: [PATCH 73/81] Commit RRsetList to trunk (mutually written and reviewed, ping-pong style, by Michael and me) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@952 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/Makefile.am | 2 + src/lib/dns/cpp/rrsetlist.cc | 67 ++++++++++ src/lib/dns/cpp/rrsetlist.h | 68 ++++++++++ src/lib/dns/cpp/rrsetlist_unittest.cc | 177 ++++++++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 src/lib/dns/cpp/rrsetlist.cc create mode 100644 src/lib/dns/cpp/rrsetlist.h create mode 100644 src/lib/dns/cpp/rrsetlist_unittest.cc diff --git a/src/lib/dns/cpp/Makefile.am b/src/lib/dns/cpp/Makefile.am index 98e2d365ef..beb8aa70be 100644 --- a/src/lib/dns/cpp/Makefile.am +++ b/src/lib/dns/cpp/Makefile.am @@ -13,6 +13,7 @@ libdns_la_SOURCES += rrclass.h rrclass.cc rrtype.h rrtype.cc rrttl.h rrttl.cc libdns_la_SOURCES += rdata.h rdata.cc libdns_la_SOURCES += rdataclass.h rdataclass.cc libdns_la_SOURCES += rrset.h rrset.cc +libdns_la_SOURCES += rrsetlist.h rrsetlist.cc libdns_la_SOURCES += question.h question.cc libdns_la_SOURCES += message.h message.cc libdns_la_SOURCES += base64.h base64.cc @@ -33,6 +34,7 @@ run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc run_unittests_SOURCES += rrttl_unittest.cc run_unittests_SOURCES += rdata_unittest.cc run_unittests_SOURCES += rrset_unittest.cc +run_unittests_SOURCES += rrsetlist_unittest.cc run_unittests_SOURCES += question_unittest.cc run_unittests_SOURCES += rrparamregistry_unittest.cc run_unittests_SOURCES += message_unittest.cc diff --git a/src/lib/dns/cpp/rrsetlist.cc b/src/lib/dns/cpp/rrsetlist.cc new file mode 100644 index 0000000000..6c1dc05b9f --- /dev/null +++ b/src/lib/dns/cpp/rrsetlist.cc @@ -0,0 +1,67 @@ +// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id$ + +#include +#include +#include + +#include +#include + +#include "rrsetlist.h" + +using namespace std; +using namespace isc::dns; + +namespace isc { +namespace dns { + +void +RRsetList::addRRset(const RRsetPtr rrsetptr) +{ + const RRsetPtr rrset_found = findRRset(rrsetptr->getType(), + rrsetptr->getClass()); + if (rrset_found) { + dns_throw(DuplicateRRset, ""); + } + rrsets_.push_back(rrsetptr); +} + +const RRsetPtr +RRsetList::findRRset(const RRsetPtr rrsetptr) +{ + BOOST_FOREACH(const RRsetPtr t, rrsets_) { + if (rrsetptr == t) { + return rrsetptr; + } + } + return RRsetPtr(); +} + +const RRsetPtr +RRsetList::findRRset(const RRType& rrtype, const RRClass& rrclass) +{ + BOOST_FOREACH(const RRsetPtr rrsetptr, rrsets_) { + const AbstractRRset* rrset = rrsetptr.get(); + if ((rrset->getClass() == rrclass) && (rrset->getType() == rrtype)) { + return rrsetptr; + } + } + return RRsetPtr(); +} + +} +} diff --git a/src/lib/dns/cpp/rrsetlist.h b/src/lib/dns/cpp/rrsetlist.h new file mode 100644 index 0000000000..b566f1d511 --- /dev/null +++ b/src/lib/dns/cpp/rrsetlist.h @@ -0,0 +1,68 @@ +// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id$ + +#ifndef __RRSETLIST_H +#define __RRSETLIST_H 1 + +#include +#include +#include + +#include + +#include "rrset.h" +#include "rrclass.h" +#include "rrtype.h" + +namespace isc { +namespace dns { + +class DuplicateRRset : public Exception { +public: + DuplicateRRset(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +class RRsetList { +public: + void addRRset(const RRsetPtr new_rrsetptr); + const RRsetPtr findRRset(const RRType& rrtype, + const RRClass& rrclass = RRClass::IN()); + const RRsetPtr findRRset(const RRsetPtr); + + const RRsetPtr operator[](RRType t) { return (this->findRRset(t)); } + + typedef std::vector::const_iterator const_iterator; + const_iterator begin() const { return (rrsets_.begin()); } + const_iterator end() const { return (rrsets_.end)(); } + + typedef std::vector::iterator iterator; + iterator begin() { return (rrsets_.begin()); } + iterator end() { return (rrsets_.end)(); } + + size_t size() const { return (rrsets_.size()); } + +private: + std::vector rrsets_; +}; + +} // end of namespace dns +} // end of namespace isc +#endif // __RRSETLIST_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/dns/cpp/rrsetlist_unittest.cc b/src/lib/dns/cpp/rrsetlist_unittest.cc new file mode 100644 index 0000000000..09910c0f30 --- /dev/null +++ b/src/lib/dns/cpp/rrsetlist_unittest.cc @@ -0,0 +1,177 @@ +// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $ + +#include +#include + +#include "rdata.h" +#include "rdataclass.h" +#include "rrclass.h" +#include "rrtype.h" +#include "rrsetlist.h" +#include "rrset.h" +#include "rrttl.h" + +#include + +#include "unittest_util.h" + +using isc::UnitTestUtil; +using namespace std; +using namespace isc::dns; +using namespace isc::dns::rdata; + +namespace { +class RRsetListTest : public ::testing::Test { +protected: + RRsetListTest() {} + void setupList(RRsetList& list); + static const in::A rdata_in_a; + static const in::AAAA rdata_in_aaaa; + static const generic::NS rdata_ns; + static const generic::SOA rdata_soa; + static const generic::CNAME rdata_cname; +}; + +const in::A RRsetListTest::rdata_in_a("192.0.2.1"); +const in::AAAA RRsetListTest::rdata_in_aaaa("2001:db8::1234"); +const generic::NS RRsetListTest::rdata_ns("ns.example.com"); +const generic::SOA RRsetListTest::rdata_soa(Name("ns.example.com"), + Name("root.example.com"), + 2010012601, 3600, 300, + 3600000, 1200); +const generic::CNAME RRsetListTest::rdata_cname("target.example.com"); + +void +RRsetListTest::setupList(RRsetList& list) { + RRsetPtr a(new RRset(Name("example.com"), RRClass::IN(), + RRType::A(), RRTTL(3600))); + RRsetPtr aaaa(new RRset(Name("example.com"), RRClass::IN(), + RRType::AAAA(), RRTTL(3600))); + RRsetPtr ns(new RRset(Name("example.com"), RRClass::IN(), + RRType::NS(), RRTTL(3600))); + RRsetPtr soa(new RRset(Name("example.com"), RRClass::IN(), + RRType::SOA(), RRTTL(3600))); + RRsetPtr cname(new RRset(Name("example.com"), RRClass::IN(), + RRType::CNAME(), RRTTL(3600))); + + a->addRdata(rdata_in_a); + aaaa->addRdata(rdata_in_aaaa); + ns->addRdata(rdata_ns); + soa->addRdata(rdata_soa); + cname->addRdata(rdata_cname); + + list.addRRset(a); + list.addRRset(aaaa); + list.addRRset(ns); + list.addRRset(soa); + list.addRRset(cname); +} + +TEST_F(RRsetListTest, emptyOnInitialCreate) { + RRsetList list; + EXPECT_EQ(list.size(), 0); +} + +TEST_F(RRsetListTest, addRRsets) { + RRsetList list; + setupList(list); + EXPECT_EQ(list.size(), 5); +} + +TEST_F(RRsetListTest, extraRRset) { + RRsetList list; + setupList(list); + RRsetPtr cname(new RRset(Name("another.example.com"), RRClass::IN(), + RRType::CNAME(), RRTTL(3600))); + EXPECT_THROW(list.addRRset(cname), DuplicateRRset); +} + +TEST_F(RRsetListTest, randomAccess) { + RRsetList list; + setupList(list); + + RRsetPtr p; + + p = list[RRType::CNAME()]; + EXPECT_TRUE(p->getType() == RRType::CNAME()); + + p = list[RRType::AAAA()]; + EXPECT_TRUE(p->getType() == RRType::AAAA()); + + p = list[RRType::NS()]; + EXPECT_TRUE(p->getType() == RRType::NS()); + + p = list[RRType::A()]; + EXPECT_TRUE(p->getType() == RRType::A()); + + p = list[RRType::SOA()]; + EXPECT_TRUE(p->getType() == RRType::SOA()); +} + +TEST_F(RRsetListTest, findRRset) { + RRsetList list; + setupList(list); + EXPECT_EQ(list[RRType::A()], list.findRRset(RRType::A(), RRClass::IN())); +} + +TEST_F(RRsetListTest, checkData) { + RRsetList list; + RRsetPtr a(new RRset(Name("example.com"), RRClass::IN(), + RRType::A(), RRTTL(3600))); + a->addRdata(rdata_in_a); + list.addRRset(a); + + RdataIteratorPtr it = list[RRType::A()]->getRdataIterator(); + it->first(); + EXPECT_FALSE(it->isLast()); + EXPECT_EQ("192.0.2.1", it->getCurrent().toText()); +} + +TEST_F(RRsetListTest, iterate) { + RRsetList list; + setupList(list); + + bool has_a, has_aaaa, has_ns, has_soa, has_cname; + int i = 0; + BOOST_FOREACH(RRsetPtr rrset, list) { + if (rrset->getType() == RRType::A()) { + has_a = true; + } + if (rrset->getType() == RRType::AAAA()) { + has_aaaa = true; + } + if (rrset->getType() == RRType::NS()) { + has_ns = true; + } + if (rrset->getType() == RRType::SOA()) { + has_soa = true; + } + if (rrset->getType() == RRType::CNAME()) { + has_cname = true; + } + ++i; + } + EXPECT_TRUE(has_a); + EXPECT_TRUE(has_aaaa); + EXPECT_TRUE(has_ns); + EXPECT_TRUE(has_soa); + EXPECT_TRUE(has_cname); + EXPECT_TRUE(i == 5); +} + +} + From 1c2ee20fa937f27690d1aa3f00774cedb86de4eb Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Wed, 24 Feb 2010 23:42:16 +0000 Subject: [PATCH 74/81] (trivial cleanup) removed an unnecessary include git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@954 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/dns/cpp/rrsetlist.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/dns/cpp/rrsetlist.h b/src/lib/dns/cpp/rrsetlist.h index b566f1d511..4b2092b4b9 100644 --- a/src/lib/dns/cpp/rrsetlist.h +++ b/src/lib/dns/cpp/rrsetlist.h @@ -19,7 +19,6 @@ #include #include -#include #include From 5281fe9e81760e599ba70ef294ad61de9c15ffb7 Mon Sep 17 00:00:00 2001 From: Likun Zhang Date: Thu, 25 Feb 2010 08:20:48 +0000 Subject: [PATCH 75/81] 1. Add unittests and help information for cmdctl and bindctl. 2. Add login idle timeout for cmdctl, default idle time is 1200 seconds. 3. Refactor some code for cmdctl. git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@961 e5f2f494-b856-4b98-b285-d166d9295462 --- configure.ac | 5 +- src/bin/bindctl/TODO | 12 + src/bin/bindctl/bindcmd.py | 85 +++--- src/bin/bindctl/bindctl.py | 70 +++-- src/bin/bindctl/cmdparse.py | 9 +- src/bin/bindctl/moduleinfo.py | 51 +--- src/bin/bindctl/unittest/bindctl_test.py | 21 +- src/bin/cmdctl/Makefile.am | 6 +- .../cmdctl/{b10-cmdctl.py.in => cmdctl.py.in} | 164 ++++++++---- src/bin/cmdctl/unittest/cmdctl_test.in | 12 + src/bin/cmdctl/unittest/cmdctl_test.py | 251 ++++++++++++++++++ 11 files changed, 515 insertions(+), 171 deletions(-) rename src/bin/cmdctl/{b10-cmdctl.py.in => cmdctl.py.in} (71%) create mode 100644 src/bin/cmdctl/unittest/cmdctl_test.in create mode 100644 src/bin/cmdctl/unittest/cmdctl_test.py diff --git a/configure.ac b/configure.ac index 354d428cfa..4c1ed4dee6 100644 --- a/configure.ac +++ b/configure.ac @@ -172,8 +172,9 @@ AC_CONFIG_FILES([Makefile ]) AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py src/bin/cfgmgr/run_b10-cfgmgr.sh - src/bin/cmdctl/b10-cmdctl.py + src/bin/cmdctl/cmdctl.py src/bin/cmdctl/run_b10-cmdctl.sh + src/bin/cmdctl/unittest/cmdctl_test src/bin/bind10/bind10.py src/bin/bind10/bind10_test src/bin/bind10/run_bind10.sh @@ -190,6 +191,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py chmod +x src/bin/cfgmgr/run_b10-cfgmgr.sh chmod +x src/bin/cmdctl/run_b10-cmdctl.sh chmod +x src/bin/bind10/run_bind10.sh + chmod +x src/bin/cmdctl/unittest/cmdctl_test + chmod +x src/bin/bindctl/unittest/bindctl_test chmod +x src/bin/bindctl/bindctl chmod +x src/bin/msgq/run_msgq.sh chmod +x src/bin/msgq/msgq_test diff --git a/src/bin/bindctl/TODO b/src/bin/bindctl/TODO index 1490c46fd1..895b5f5f1d 100644 --- a/src/bin/bindctl/TODO +++ b/src/bin/bindctl/TODO @@ -1,3 +1,15 @@ 1. Refactor the code for bindctl. 2. Update man page for bindctl provided by jreed. 3. Add more unit tests. +4. Need Review: + bindcmd.py: + apply_config_cmd() + _validate_cmd() + complete() + + cmdparse.py: + _parse_params + + moduleinfo.py: + get_param_name_by_position + diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index 88b2984e3b..a33d3239fa 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -61,47 +61,52 @@ class BindCmdInterpreter(Cmd): self.modules = OrderedDict() self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl")) self.server_port = server_port - self.connect_to_cmd_ctrld() + self._connect_to_cmd_ctrld() self.session_id = self._get_session_id() - def connect_to_cmd_ctrld(self): + def _connect_to_cmd_ctrld(self): + '''Connect to cmdctl in SSL context. ''' try: self.conn = http.client.HTTPSConnection(self.server_port, cert_file='bindctl.pem') except Exception as e: - print(e) - print("can't connect to %s, please make sure cmd-ctrld is running" % self.server_port) + print(e, "can't connect to %s, please make sure cmd-ctrld is running" % + self.server_port) def _get_session_id(self): + '''Generate one session id for the connection. ''' rand = os.urandom(16) now = time.time() ip = socket.gethostbyname(socket.gethostname()) session_id = sha1(("%s%s%s" %(rand, now, ip)).encode()) - session_id = session_id.hexdigest() - return session_id + digest = session_id.hexdigest() + return digest def run(self): + '''Parse commands inputted from user and send them to cmdctl. ''' try: - ret = self.login() - if not ret: + if not self.login_to_cmdctl(): return False # Get all module information from cmd-ctrld self.config_data = isc.config.UIModuleCCSession(self) - self.update_commands() + self._update_commands() self.cmdloop() except KeyboardInterrupt: return True - def login(self): + def login_to_cmdctl(self): + '''Login to cmdctl with the username and password inputted + from user. After login sucessfully, the username and password + will be saved in 'default_user.csv', when login next time, + username and password saved in 'default_user.csv' will be used + first. + ''' csvfile = None bsuccess = False try: csvfile = open('default_user.csv') users = csv.reader(csvfile) for row in users: - if (len(row) < 2): - continue - param = {'username': row[0], 'password' : row[1]} response = self.send_POST('/login', param) data = response.read().decode() @@ -120,10 +125,13 @@ class BindCmdInterpreter(Cmd): return True count = 0 - csvfile = None print("[TEMP MESSAGE]: username :root password :bind10") - while count < 3: + while True: count = count + 1 + if count > 3: + print("Too many authentication failures") + return False + username = input("Username:") passwd = getpass.getpass() param = {'username': username, 'password' : passwd} @@ -135,26 +143,23 @@ class BindCmdInterpreter(Cmd): csvfile = open('default_user.csv', 'w') writer = csv.writer(csvfile) writer.writerow([username, passwd]) - bsuccess = True - break + csvfile.close() + return True - if count == 3: - print("Too many authentication failures") - break - if csvfile: - csvfile.close() - return bsuccess - - def update_commands(self): + def _update_commands(self): + '''Get all commands of modules. ''' cmd_spec = self.send_GET('/command_spec') - if (len(cmd_spec) == 0): - print('can\'t get any command specification') + if not cmd_spec: + return + for module_name in cmd_spec.keys(): - if cmd_spec[module_name]: - self.prepare_module_commands(module_name, cmd_spec[module_name]) + self._prepare_module_commands(module_name, cmd_spec[module_name]) def send_GET(self, url, body = None): + '''Send GET request to cmdctl, session id is send with the name + 'cookie' in header. + ''' headers = {"cookie" : self.session_id} self.conn.request('GET', url, body, headers) res = self.conn.getresponse() @@ -162,11 +167,12 @@ class BindCmdInterpreter(Cmd): if reply_msg: return json.loads(reply_msg.decode()) else: - return None + return {} def send_POST(self, url, post_param = None): - ''' + '''Send GET request to cmdctl, session id is send with the name + 'cookie' in header. Format: /module_name/command_name parameters of command is encoded as a map ''' @@ -183,13 +189,12 @@ class BindCmdInterpreter(Cmd): self.prompt = self.location + self.prompt_end return stop - def prepare_module_commands(self, module_name, module_commands): + def _prepare_module_commands(self, module_name, module_commands): module = ModuleInfo(name = module_name, desc = "same here") for command in module_commands: cmd = CommandInfo(name = command["command_name"], - desc = command["command_description"], - need_inst_param = False) + desc = command["command_description"]) for arg in command["command_args"]: param = ParamInfo(name = arg["item_name"], type = arg["item_type"], @@ -200,7 +205,7 @@ class BindCmdInterpreter(Cmd): module.add_command(cmd) self.add_module_info(module) - def validate_cmd(self, cmd): + def _validate_cmd(self, cmd): if not cmd.module in self.modules: raise CmdUnknownModuleSyntaxError(cmd.module) @@ -225,7 +230,6 @@ class BindCmdInterpreter(Cmd): list(params.keys())[0]) elif params: param_name = None - index = 0 param_count = len(params) for name in params: # either the name of the parameter must be known, or @@ -250,18 +254,17 @@ class BindCmdInterpreter(Cmd): raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, cmd.params[name]) else: # replace the numbered items by named items - param_name = command_info.get_param_name_by_position(name+1, index, param_count) + param_name = command_info.get_param_name_by_position(name+1, param_count) cmd.params[param_name] = cmd.params[name] del cmd.params[name] elif not name in all_params: raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, name) + param_nr = 0 for name in manda_params: if not name in params and not param_nr in params: raise CmdMissParamSyntaxError(cmd.module, cmd.command, name) - - param_nr += 1 param_nr += 1 def _handle_cmd(self, cmd): @@ -385,7 +388,7 @@ class BindCmdInterpreter(Cmd): def _parse_cmd(self, line): try: cmd = BindCmdParse(line) - self.validate_cmd(cmd) + self._validate_cmd(cmd) self._handle_cmd(cmd) except BindCtlException as e: print("Error! ", e) @@ -497,5 +500,3 @@ class BindCmdInterpreter(Cmd): print("received reply:", data) - - diff --git a/src/bin/bindctl/bindctl.py b/src/bin/bindctl/bindctl.py index 6e5715efed..0f56592fe2 100644 --- a/src/bin/bindctl/bindctl.py +++ b/src/bin/bindctl/bindctl.py @@ -18,69 +18,97 @@ from moduleinfo import * from bindcmd import * import isc import pprint +from optparse import OptionParser, OptionValueError +__version__ = 'Bindctl' def prepare_config_commands(tool): module = ModuleInfo(name = "config", desc = "Configuration commands") - cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False) + cmd = CommandInfo(name = "show", desc = "Show configuration") param = ParamInfo(name = "identifier", type = "string", optional=True) cmd.add_param(param) module.add_command(cmd) - cmd = CommandInfo(name = "add", desc = "Add entry to configuration list", need_inst_param = False) + cmd = CommandInfo(name = "add", desc = "Add entry to configuration list") param = ParamInfo(name = "identifier", type = "string", optional=True) cmd.add_param(param) param = ParamInfo(name = "value", type = "string", optional=False) cmd.add_param(param) module.add_command(cmd) - cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list", need_inst_param = False) + cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list") param = ParamInfo(name = "identifier", type = "string", optional=True) cmd.add_param(param) param = ParamInfo(name = "value", type = "string", optional=False) cmd.add_param(param) module.add_command(cmd) - cmd = CommandInfo(name = "set", desc = "Set a configuration value", need_inst_param = False) + cmd = CommandInfo(name = "set", desc = "Set a configuration value") param = ParamInfo(name = "identifier", type = "string", optional=True) cmd.add_param(param) param = ParamInfo(name = "value", type = "string", optional=False) cmd.add_param(param) module.add_command(cmd) - cmd = CommandInfo(name = "unset", desc = "Unset a configuration value", need_inst_param = False) + cmd = CommandInfo(name = "unset", desc = "Unset a configuration value") param = ParamInfo(name = "identifier", type = "string", optional=False) cmd.add_param(param) module.add_command(cmd) - cmd = CommandInfo(name = "diff", desc = "Show all local changes", need_inst_param = False) + cmd = CommandInfo(name = "diff", desc = "Show all local changes") module.add_command(cmd) - cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False) + cmd = CommandInfo(name = "revert", desc = "Revert all local changes") module.add_command(cmd) - cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False) + cmd = CommandInfo(name = "commit", desc = "Commit all local changes") module.add_command(cmd) - cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part", need_inst_param = False) + cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part") param = ParamInfo(name = "identifier", type="string", optional=False) cmd.add_param(param) module.add_command(cmd) tool.add_module_info(module) +def check_port(option, opt_str, value, parser): + if (value < 0) or (value > 65535): + raise OptionValueError('%s requires a port number (0-65535)' % opt_str) + parser.values.port = value + +def check_addr(option, opt_str, value, parser): + ipstr = value + ip_family = socket.AF_INET + if (ipstr.find(':') != -1): + ip_family = socket.AF_INET6 + + try: + socket.inet_pton(ip_family, ipstr) + except: + raise OptionValueError("%s invalid ip address" % ipstr) + + parser.values.addr = value + +def set_bindctl_options(parser): + parser.add_option('-p', '--port', dest = 'port', type = 'int', + action = 'callback', callback=check_port, + default = '8080', help = 'port for cmdctl of bind10') + + parser.add_option('-a', '--address', dest = 'addr', type = 'string', + action = 'callback', callback=check_addr, + default = '127.0.0.1', help = 'IP address for cmdctl of bind10') + + if __name__ == '__main__': - tool = BindCmdInterpreter("localhost:8080") - prepare_config_commands(tool) - tool.run() -# TODO: put below back, was removed to see errors -#if __name__ == '__main__': - #try: - #tool = BindCmdInterpreter("localhost:8080") - #prepare_config_commands(tool) - #tool.run() - #except Exception as e: - #print(e) - #print("Failed to connect with b10-cmdctl module, is it running?") + try: + parser = OptionParser(version = __version__) + set_bindctl_options(parser) + (options, args) = parser.parse_args() + server_addr = options.addr + ':' + str(options.port) + tool = BindCmdInterpreter(server_addr) + prepare_config_commands(tool) + tool.run() + except Exception as e: + print(e, "\nFailed to connect with b10-cmdctl module, is it running?") diff --git a/src/bin/bindctl/cmdparse.py b/src/bin/bindctl/cmdparse.py index dac46699f6..8b51db238e 100644 --- a/src/bin/bindctl/cmdparse.py +++ b/src/bin/bindctl/cmdparse.py @@ -34,10 +34,10 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str) NAME_PATTERN = re.compile("^\s*(?P[\w]+)(?P\s*)(?P.*)$") class BindCmdParse: - """ This class will parse the command line user input into three parts: - module name, command, parameters. - The first two parts are strings and parameter is one hash. - The parameter part is optional. + """ This class will parse the command line usr input into three part + module name, command, parameters + the first two parts are strings and parameter is one hash, + parameters part is optional Example: zone reload, zone_name=example.com module == zone @@ -52,6 +52,7 @@ class BindCmdParse: self._parse_cmd(cmd) def _parse_cmd(self, text_str): + '''Parse command line. ''' # Get module name groups = NAME_PATTERN.match(text_str) if not groups: diff --git a/src/bin/bindctl/moduleinfo.py b/src/bin/bindctl/moduleinfo.py index a3438d4d4e..791486ffb5 100644 --- a/src/bin/bindctl/moduleinfo.py +++ b/src/bin/bindctl/moduleinfo.py @@ -51,10 +51,8 @@ class CommandInfo: more parameters """ - def __init__(self, name, desc = "", need_inst_param = True): + def __init__(self, name, desc = ""): self.name = name - # Wether command needs parameter "instance_name" - self.need_inst_param = need_inst_param self.desc = desc self.params = OrderedDict() # Set default parameter "help" @@ -91,7 +89,7 @@ class CommandInfo: return [name for name in all_names if not self.params[name].is_optional] - def get_param_name_by_position(self, pos, index, param_count): + def get_param_name_by_position(self, pos, param_count): # count mandatories back from the last # from the last mandatory; see the number of mandatories before it # and compare that to the number of positional arguments left to do @@ -101,7 +99,9 @@ class CommandInfo: # (can this be done in all cases? this is certainly not the most efficient method; # one way to make the whole of this more consistent is to always set mandatories first, but # that would make some commands less nice to use ("config set value location" instead of "config set location value") - if type(pos) == int: + if type(pos) != int: + raise KeyError(str(pos) + " is not an integer") + else: if param_count == len(self.params) - 1: i = 0 for k in self.params.keys(): @@ -131,14 +131,9 @@ class CommandInfo: raise KeyError(str(pos) + " out of range") else: raise KeyError("Too many parameters") - else: - raise KeyError(str(pos) + " is not an integer") - - def need_instance_param(self): - return self.need_inst_param - def command_help(self, inst_name, inst_type, inst_desc): + def command_help(self): print("Command ", self) print("\t\thelp (Get help for command)") @@ -166,65 +161,39 @@ class CommandInfo: class ModuleInfo: """Define the information of one module, include module name, - module supporting commands, instance name and the value type of instance name + module supporting commands. """ - def __init__(self, name, inst_name = "", inst_type = STRING_TYPE, - inst_desc = "", desc = ""): + def __init__(self, name, desc = ""): self.name = name - self.inst_name = inst_name - self.inst_type = inst_type - self.inst_desc = inst_desc self.desc = desc self.commands = OrderedDict() self.add_command(CommandInfo(name = "help", - desc = "Get help for module", - need_inst_param = False)) + desc = "Get help for module")) def __str__(self): return str("%s \t%s" % (self.name, self.desc)) def add_command(self, command_info): self.commands[command_info.name] = command_info - if command_info.need_instance_param(): - command_info.add_param(ParamInfo(name = self.inst_name, - type = self.inst_type, - desc = self.inst_desc)) - def has_command_with_name(self, command_name): return command_name in self.commands - def get_command_with_name(self, command_name): return self.commands[command_name] - def get_commands(self): return list(self.commands.values()) - def get_command_names(self): return list(self.commands.keys()) - - - def get_instance_param_name(self): - return self.inst_name - - - def get_instance_param_type(self): - return self.inst_type - def module_help(self): print("Module ", self, "\nAvailable commands:") for k in self.commands.keys(): print("\t", self.commands[k]) - def command_help(self, command): - self.commands[command].command_help(self.inst_name, - self.inst_type, - self.inst_desc) - + self.commands[command].command_help() diff --git a/src/bin/bindctl/unittest/bindctl_test.py b/src/bin/bindctl/unittest/bindctl_test.py index e9775fe626..a794fd86f8 100644 --- a/src/bin/bindctl/unittest/bindctl_test.py +++ b/src/bin/bindctl/unittest/bindctl_test.py @@ -85,16 +85,6 @@ class TestCmdLex(unittest.TestCase): self.my_assert_raise(CmdCommandNameFormatError, "zone z-d ") self.my_assert_raise(CmdCommandNameFormatError, "zone zdd/") self.my_assert_raise(CmdCommandNameFormatError, "zone zdd/ \"") - - - def testCmdParamFormatError(self): - self.my_assert_raise(CmdParamFormatError, "zone load load") - self.my_assert_raise(CmdParamFormatError, "zone load load=") - self.my_assert_raise(CmdParamFormatError, "zone load load==dd") - self.my_assert_raise(CmdParamFormatError, "zone load , zone_name=dd zone_file=d" ) - self.my_assert_raise(CmdParamFormatError, "zone load zone_name=dd zone_file" ) - self.my_assert_raise(CmdParamFormatError, "zone zdd \"") - class TestCmdSyntax(unittest.TestCase): @@ -103,18 +93,21 @@ class TestCmdSyntax(unittest.TestCase): tool = bindcmd.BindCmdInterpreter() zone_file_param = ParamInfo(name = "zone_file") + zone_name = ParamInfo(name = 'zone_name') load_cmd = CommandInfo(name = "load") load_cmd.add_param(zone_file_param) + load_cmd.add_param(zone_name) param_master = ParamInfo(name = "master", optional = True) param_allow_update = ParamInfo(name = "allow_update", optional = True) set_cmd = CommandInfo(name = "set") set_cmd.add_param(param_master) set_cmd.add_param(param_allow_update) + set_cmd.add_param(zone_name) - reload_all_cmd = CommandInfo(name = "reload_all", need_inst_param = False) + reload_all_cmd = CommandInfo(name = "reload_all") - zone_module = ModuleInfo(name = "zone", inst_name = "zone_name") + zone_module = ModuleInfo(name = "zone") zone_module.add_command(load_cmd) zone_module.add_command(set_cmd) zone_module.add_command(reload_all_cmd) @@ -129,12 +122,12 @@ class TestCmdSyntax(unittest.TestCase): def no_assert_raise(self, cmd_line): cmd = cmdparse.BindCmdParse(cmd_line) - self.bindcmd.validate_cmd(cmd) + self.bindcmd._validate_cmd(cmd) def my_assert_raise(self, exception_type, cmd_line): cmd = cmdparse.BindCmdParse(cmd_line) - self.assertRaises(exception_type, self.bindcmd.validate_cmd, cmd) + self.assertRaises(exception_type, self.bindcmd._validate_cmd, cmd) def testValidateSuccess(self): diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am index 5465a6bead..6916c54596 100644 --- a/src/bin/cmdctl/Makefile.am +++ b/src/bin/cmdctl/Makefile.am @@ -5,10 +5,10 @@ pkglibexec_SCRIPTS = b10-cmdctl b10_cmdctldir = $(DESTDIR)$(pkgdatadir) b10_cmdctl_DATA = passwd.csv b10-cmdctl.pem -CLEANFILES= b10-cmdctl +CLEANFILES= cmdctl.py # TODO: does this need $$(DESTDIR) also? # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix -b10-cmdctl: b10-cmdctl.py - $(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cmdctl.py >$@ +b10-cmdctl: cmdctl.py + $(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@ chmod a+x $@ diff --git a/src/bin/cmdctl/b10-cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in similarity index 71% rename from src/bin/cmdctl/b10-cmdctl.py.in rename to src/bin/cmdctl/cmdctl.py.in index d9c576c164..0c2a277c3e 100644 --- a/src/bin/cmdctl/b10-cmdctl.py.in +++ b/src/bin/cmdctl/cmdctl.py.in @@ -38,12 +38,16 @@ import pprint import select import csv import random +import time +import signal +from optparse import OptionParser, OptionValueError from hashlib import sha1 try: import threading except ImportError: import dummy_threading as threading +__version__ = 'BIND10' URL_PATTERN = re.compile('/([\w]+)(?:/([\w]+))?/?') # If B10_FROM_SOURCE is set in the environment, we use data files @@ -92,7 +96,16 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): return self.session_id def _is_user_logged_in(self): - return self.session_id in self.server.user_sessions + login_time = self.server.user_sessions.get(self.session_id) + if not login_time: + return False + + idle_time = time.time() - login_time + if idle_time > self.server.idle_timeout: + return False + # Update idle time + self.server.user_sessions[self.session_id] = time.time() + return True def _parse_request_path(self): '''Parse the url, the legal url should like /ldh or /ldh/ldh ''' @@ -103,6 +116,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): return (groups.group(1), groups.group(2)) def do_POST(self): + '''Process POST request. ''' '''Process user login and send command to proper module The client should send its session id in header with the name 'cookie' @@ -112,8 +126,10 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): if self._is_session_valid(): if self.path == '/login': rcode, reply = self._handle_login() - else: + elif self._is_user_logged_in(): rcode, reply = self._handle_post_request() + else: + rcode, reply = http.client.UNAUTHORIZED, ["please login"] else: rcode, reply = http.client.BAD_REQUEST, ["session isn't valid"] @@ -127,19 +143,22 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): return http.client.OK, ["user has already login"] is_user_valid, error_info = self._check_user_name_and_pwd() if is_user_valid: - self.server.user_sessions.append(self.session_id) + self.server.save_user_session_id(self.session_id) return http.client.OK, ["login success "] else: return http.client.UNAUTHORIZED, error_info def _check_user_name_and_pwd(self): + '''Check user name and its password ''' length = self.headers.get('Content-Length') if not length: return False, ["invalid username or password"] - user_info = json.loads((self.rfile.read(int(length))).decode()) - if not user_info: - return False, ["invalid username or password"] - + + try: + user_info = json.loads((self.rfile.read(int(length))).decode()) + except: + return False, ["invalid username or password"] + user_name = user_info.get('username') if not user_name: return False, ["need user name"] @@ -158,21 +177,24 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler): def _handle_post_request(self): + '''Handle all the post request from client. ''' mod, cmd = self._parse_request_path() + if (not mod) or (not cmd): + return http.client.BAD_REQUEST, ['malformed url'] + param = None len = self.headers.get('Content-Length') - rcode = http.client.OK - reply = None if len: - post_str = str(self.rfile.read(int(len)).decode()) - print("command parameter:%s" % post_str) - param = json.loads(post_str) - # TODO, need return some proper return code. - # currently always OK. - reply = self.server.send_command_to_module(mod, cmd, param) - print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod)) + try: + post_str = str(self.rfile.read(int(len)).decode()) + param = json.loads(post_str) + except: + pass - return rcode, reply + reply = self.server.send_command_to_module(mod, cmd, param) + print('b10-cmdctl finish send message \'%s\' to module %s' % (cmd, mod)) + # TODO, need set proper rcode + return http.client.OK, reply class CommandControl(): @@ -192,9 +214,11 @@ class CommandControl(): return self.send_command('ConfigManager', 'get_commands_spec') def get_config_data(self): + '''Get config data for all modules from configmanager ''' return self.send_command('ConfigManager', 'get_config') def update_config_data(self, module_name, command_name): + '''Get lastest config data for all modules from configmanager ''' if module_name == 'ConfigManager' and command_name == 'set_config': self.config_data = self.get_config_data() @@ -202,7 +226,7 @@ class CommandControl(): return self.send_command('ConfigManager', 'get_module_spec') def handle_recv_msg(self): - # Handle received message, if 'shutdown' is received, return False + '''Handle received message, if 'shutdown' is received, return False''' (message, env) = self.cc.group_recvmsg(True) while message: if 'commands_update' in message: @@ -217,44 +241,44 @@ class CommandControl(): return True def send_command(self, module_name, command_name, params = None): + '''Send the command from bindctl to proper module. ''' content = [command_name] if params: content.append(params) - msg = {'command' : content} + reply = {} print('b10-cmdctl send command \'%s\' to %s' %(command_name, module_name)) try: + msg = {'command' : content} self.cc.group_sendmsg(msg, module_name) - #TODO, it may be blocked, msqg need to add a new interface - # wait in timeout. + #TODO, it may be blocked, msqg need to add a new interface waiting in timeout. answer, env = self.cc.group_recvmsg(False) - if answer and 'result' in answer.keys() and type(answer['result']) == list: - # TODO: with the new cc implementation, replace "1" by 1 - if answer['result'][0] == 1: + if answer and 'result' in answer.keys() and type(answer['result']) == list: + if answer['result'][0] != 0: # todo: exception - print("Error: " + str(answer['result'][1])) - return {} + print("Error: " + str(answer['result'][1])) else: self.update_config_data(module_name, command_name) - if len(answer['result']) > 1: - return answer['result'][1] - return {} + if (len(answer['result']) > 1): + reply = answer['result'][1] else: print("Error: unexpected answer from %s" % module_name) print(answer) except Exception as e: - print(e) - print('b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name)) - return {} + print(e, ':b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name)) + + return reply class SecureHTTPServer(http.server.HTTPServer): '''Make the server address can be reused.''' allow_reuse_address = True - def __init__(self, server_address, RequestHandlerClass): + def __init__(self, server_address, RequestHandlerClass, idle_timeout = 1200): + '''idle_timeout: the max idle time for login''' http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass) - self.user_sessions = [] + self.user_sessions = {} + self.idle_timeout = idle_timeout self.cmdctrl = CommandControl() self.__is_shut_down = threading.Event() self.__serving = False @@ -262,23 +286,25 @@ class SecureHTTPServer(http.server.HTTPServer): self._read_user_info() def _read_user_info(self): - # Get all username and password information + '''Read all user's name and its' password from csv file.''' csvfile = None try: csvfile = open(USER_INFO_FILE) reader = csv.reader(csvfile) for row in reader: self.user_infos[row[0]] = [row[1], row[2]] - except Exception as e: print("Fail to read user information ", e) - exit(1) finally: if csvfile: csvfile.close() + def save_user_session_id(self, session_id): + # Record user's id and login time. + self.user_sessions[session_id] = time.time() def get_request(self): + '''Get client request socket and wrap it in SSL context. ''' newsocket, fromaddr = self.socket.accept() try: connstream = ssl.wrap_socket(newsocket, @@ -298,18 +324,18 @@ class SecureHTTPServer(http.server.HTTPServer): '''Currently only support the following three url GET request ''' rcode, reply = http.client.NO_CONTENT, [] if not module: - rcode = http.client.OK if id == 'command_spec': - reply = self.cmdctrl.command_spec + rcode, reply = http.client.OK, self.cmdctrl.command_spec elif id == 'config_data': - reply = self.cmdctrl.config_data + rcode, reply = http.client.OK, self.cmdctrl.config_data elif id == 'config_spec': - reply = self.cmdctrl.config_spec + rcode, reply = http.client.OK, self.cmdctrl.config_spec return rcode, reply def serve_forever(self, poll_interval = 0.5): + '''Start cmdctl as one tcp server. ''' self.__serving = True self.__is_shut_down.clear() while self.__serving: @@ -330,21 +356,69 @@ class SecureHTTPServer(http.server.HTTPServer): def send_command_to_module(self, module_name, command_name, params): return self.cmdctrl.send_command(module_name, command_name, params) +httpd = None -def run(server_class = SecureHTTPServer, addr = 'localhost', port = 8080): +def signal_handler(signal, frame): + if httpd: + httpd.shutdown() + sys.exit(0) + +def set_signal_handler(): + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGINT, signal_handler) + +def run(addr = 'localhost', port = 8080, idle_timeout = 1200): ''' Start cmdctl as one https server. ''' print("b10-cmdctl module is starting on :%s port:%d" %(addr, port)) - httpd = server_class((addr, port), SecureHTTPRequestHandler) + httpd = SecureHTTPServer((addr, port), SecureHTTPRequestHandler, idle_timeout) httpd.serve_forever() +def check_port(option, opt_str, value, parser): + if (value < 0) or (value > 65535): + raise OptionValueError('%s requires a port number (0-65535)' % opt_str) + parser.values.port = value + +def check_addr(option, opt_str, value, parser): + ipstr = value + ip_family = socket.AF_INET + if (ipstr.find(':') != -1): + ip_family = socket.AF_INET6 + + try: + socket.inet_pton(ip_family, ipstr) + except: + raise OptionValueError("%s invalid ip address" % ipstr) + + parser.values.addr = value + +def set_cmd_options(parser): + parser.add_option('-p', '--port', dest = 'port', type = 'int', + action = 'callback', callback=check_port, + default = '8080', help = 'port cmdctl will use') + + parser.add_option('-a', '--address', dest = 'addr', type = 'string', + action = 'callback', callback=check_addr, + default = '127.0.0.1', help = 'IP address cmdctl will use') + + parser.add_option('-i', '--idle-timeout', dest = 'idle_timeout', type = 'int', + default = '1200', help = 'login idle time out') + if __name__ == '__main__': try: - run() + parser = OptionParser(version = __version__) + set_cmd_options(parser) + (options, args) = parser.parse_args() + set_signal_handler() + run(options.addr, options.port, options.idle_timeout) except isc.cc.SessionError as se: print("[b10-cmdctl] Error creating b10-cmdctl, " "is the command channel daemon running?") except KeyboardInterrupt: print("exit http server") - + + if httpd: + httpd.shutdown() + + diff --git a/src/bin/cmdctl/unittest/cmdctl_test.in b/src/bin/cmdctl/unittest/cmdctl_test.in new file mode 100644 index 0000000000..be86729274 --- /dev/null +++ b/src/bin/cmdctl/unittest/cmdctl_test.in @@ -0,0 +1,12 @@ +#! /bin/sh + +PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@} +export PYTHON_EXEC + +BINDCTL_TEST_PATH=@abs_top_srcdir@/src/bin/cmdctl/unittest +PYTHONPATH=@abs_top_srcdir@/src/bin/cmdctl +export PYTHONPATH + +cd ${BINDCTL_TEST_PATH} +exec ${PYTHON_EXEC} -O cmdctl_test.py $* + diff --git a/src/bin/cmdctl/unittest/cmdctl_test.py b/src/bin/cmdctl/unittest/cmdctl_test.py new file mode 100644 index 0000000000..2255176b96 --- /dev/null +++ b/src/bin/cmdctl/unittest/cmdctl_test.py @@ -0,0 +1,251 @@ +# Copyright (C) 2009 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 socket +from cmdctl import * + +# Rewrite the class for unittest. +class MySecureHTTPRequestHandler(SecureHTTPRequestHandler): + def __init__(self): + pass + + def send_response(self, rcode): + self.rcode = rcode + + def end_headers(self): + pass + + def do_GET(self): + self.wfile = open('tmp.file', 'wb') + super().do_GET() + self.wfile.close() + os.remove('tmp.file') + + def do_POST(self): + self.wfile = open("tmp.file", 'wb') + super().do_POST() + self.wfile.close() + os.remove('tmp.file') + + +class MySecureHTTPServer(SecureHTTPServer): + def __init__(self): + self.user_sessions = {} + self.idle_timeout = 1200 + self.cmdctrl = MyCommandControl() + +class MyCommandControl(): + def __init__(self): + self.command_spec = [] + self.config_spec = [] + self.config_data = [] + + def send_command(self, mod, cmd, param): + pass + + +class TestSecureHTTPRequestHandler(unittest.TestCase): + def setUp(self): + self.handler = MySecureHTTPRequestHandler() + self.handler.server = MySecureHTTPServer() + self.handler.server.user_sessions = {} + self.handler.server.user_infos = {} + self.handler.headers = {} + + def test_parse_request_path(self): + self.handler.path = '' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == None) and (cmd == None)) + + self.handler.path = '/abc' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == 'abc') and (cmd == None)) + + self.handler.path = '/abc/edf' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == 'abc') and (cmd == 'edf')) + + self.handler.path = '/abc/edf/ghi' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == 'abc') and (cmd == 'edf')) + + def test_parse_request_path_1(self): + self.handler.path = '/ab*c' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == 'ab') and cmd == None) + + self.handler.path = '/abc/ed*fdd/ddd' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == 'abc') and cmd == 'ed') + + self.handler.path = '/-*/edfdd/ddd' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == None) and (cmd == None)) + + self.handler.path = '/-*/edfdd/ddd' + mod, cmd = self.handler._parse_request_path() + self.assertTrue((mod == None) and (cmd == None)) + + def test_do_GET(self): + self.handler.do_GET() + self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST) + + def test_do_GET_1(self): + self.handler.headers['cookie'] = 12345 + self.handler.do_GET() + self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED) + + def test_do_GET_2(self): + self.handler.headers['cookie'] = 12345 + self.handler.server.user_sessions[12345] = time.time() + 1000000 + self.handler.path = '/how/are' + self.handler.do_GET() + self.assertEqual(self.handler.rcode, http.client.NO_CONTENT) + + def test_do_GET_3(self): + self.handler.headers['cookie'] = 12346 + self.handler.server.user_sessions[12346] = time.time() + 1000000 + path_vec = ['command_spec', 'config_data', 'config_spec'] + for path in path_vec: + self.handler.path = '/' + path + self.handler.do_GET() + self.assertEqual(self.handler.rcode, http.client.OK) + + def test_user_logged_in(self): + self.handler.server.user_sessions = {} + self.handler.session_id = 12345 + self.assertTrue(self.handler._is_user_logged_in() == False) + + self.handler.server.user_sessions[12345] = time.time() + self.assertTrue(self.handler._is_user_logged_in()) + + self.handler.server.user_sessions[12345] = time.time() - 1500 + self.handler.idle_timeout = 1200 + self.assertTrue(self.handler._is_user_logged_in() == False) + + def test_check_user_name_and_pwd(self): + self.handler.headers = {} + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['invalid username or password']) + + def test_check_user_name_and_pwd_1(self): + self.handler.rfile = open("check.tmp", 'w+b') + user_info = {'username':'root', 'password':'abc123'} + len = self.handler.rfile.write(json.dumps(user_info).encode()) + self.handler.headers['Content-Length'] = len + self.handler.rfile.seek(0, 0) + + self.handler.server.user_infos['root'] = ['aa', 'aaa'] + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['password doesn\'t match']) + self.handler.rfile.close() + os.remove('check.tmp') + + def test_check_user_name_and_pwd_2(self): + self.handler.rfile = open("check.tmp", 'w+b') + user_info = {'username':'root', 'password':'abc123'} + len = self.handler.rfile.write(json.dumps(user_info).encode()) + self.handler.headers['Content-Length'] = len - 1 + self.handler.rfile.seek(0, 0) + + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['invalid username or password']) + self.handler.rfile.close() + os.remove('check.tmp') + + def test_check_user_name_and_pwd_3(self): + self.handler.rfile = open("check.tmp", 'w+b') + user_info = {'usernae':'root', 'password':'abc123'} + len = self.handler.rfile.write(json.dumps(user_info).encode()) + self.handler.headers['Content-Length'] = len + self.handler.rfile.seek(0, 0) + + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['need user name']) + self.handler.rfile.close() + os.remove('check.tmp') + + def test_check_user_name_and_pwd_4(self): + self.handler.rfile = open("check.tmp", 'w+b') + user_info = {'username':'root', 'pssword':'abc123'} + len = self.handler.rfile.write(json.dumps(user_info).encode()) + self.handler.headers['Content-Length'] = len + self.handler.rfile.seek(0, 0) + + self.handler.server.user_infos['root'] = ['aa', 'aaa'] + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['need password']) + self.handler.rfile.close() + os.remove('check.tmp') + + def test_check_user_name_and_pwd_5(self): + self.handler.rfile = open("check.tmp", 'w+b') + user_info = {'username':'root', 'password':'abc123'} + len = self.handler.rfile.write(json.dumps(user_info).encode()) + self.handler.headers['Content-Length'] = len + self.handler.rfile.seek(0, 0) + + ret, msg = self.handler._check_user_name_and_pwd() + self.assertTrue(ret == False) + self.assertEqual(msg, ['user doesn\'t exist']) + self.handler.rfile.close() + os.remove('check.tmp') + + def test_do_POST(self): + self.handler.headers = {} + self.handler.do_POST() + self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST) + + def test_do_POST_1(self): + self.handler.headers = {} + self.handler.headers['cookie'] = 12345 + self.handler.path = '/' + self.handler.do_POST() + self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED) + + def test_handle_post_request(self): + self.handler.path = '/cfgmgr/revert' + self.handler.headers = {} + rcode, reply = self.handler._handle_post_request() + self.assertEqual(http.client.OK, rcode) + + def test_handle_post_request_1(self): + self.handler.path = '/*d/revert' + self.handler.headers = {} + rcode, reply = self.handler._handle_post_request() + self.assertEqual(http.client.BAD_REQUEST, rcode) + + def test_handle_post_request_2(self): + self.handler.rfile = open("check.tmp", 'w+b') + params = {123:'param data'} + len = self.handler.rfile.write(json.dumps(params).encode()) + self.handler.headers['Content-Length'] = len + self.handler.rfile.seek(0, 0) + self.handler.rfile.close() + os.remove('check.tmp') + + self.handler.path = '/d/revert' + rcode, reply = self.handler._handle_post_request() + self.assertEqual(http.client.OK, rcode) + +if __name__== "__main__": + unittest.main() From 896f1b7a8c4f25ddc03e43778e356c747453a5a9 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 25 Feb 2010 09:37:15 +0000 Subject: [PATCH 76/81] make cmdctl use message create functions git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@962 e5f2f494-b856-4b98-b285-d166d9295462 --- src/bin/cmdctl/cmdctl.py.in | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in index 0c2a277c3e..42b5967532 100644 --- a/src/bin/cmdctl/cmdctl.py.in +++ b/src/bin/cmdctl/cmdctl.py.in @@ -211,59 +211,59 @@ class CommandControl(): self.config_data = self.get_config_data() def get_cmd_specification(self): - return self.send_command('ConfigManager', 'get_commands_spec') + return self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_COMMANDS_SPEC) def get_config_data(self): '''Get config data for all modules from configmanager ''' - return self.send_command('ConfigManager', 'get_config') + return self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_CONFIG) def update_config_data(self, module_name, command_name): '''Get lastest config data for all modules from configmanager ''' - if module_name == 'ConfigManager' and command_name == 'set_config': + if module_name == 'ConfigManager' and command_name == isc.config.ccsession.COMMAND_SET_CONFIG: self.config_data = self.get_config_data() def get_data_specification(self): - return self.send_command('ConfigManager', 'get_module_spec') + return self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_MODULE_SPEC) def handle_recv_msg(self): '''Handle received message, if 'shutdown' is received, return False''' (message, env) = self.cc.group_recvmsg(True) - while message: - if 'commands_update' in message: - self.command_spec[message['commands_update'][0]] = message['commands_update'][1] - elif 'specification_update' in message: - msgvalue = message['specification_update'] - self.config_spec[msgvalue[0]] = msgvalue[1] - elif 'command' in message and message['command'][0] == 'shutdown': - return False; + command, arg = isc.config.ccsession.parse_command(message) + while command: + if command == isc.config.ccsession.COMMAND_COMMANDS_UPDATE: + self.command_spec[arg[0]] = arg[1] + elif command == isc.config.ccsession.COMMAND_SPECIFICATION_UPDATE: + self.config_spec[arg[0]] = arg[1] + elif command == "shutdown": + return False (message, env) = self.cc.group_recvmsg(True) + command, arg = isc.config.ccsession.parse_command(message) return True - def send_command(self, module_name, command_name, params = None): + def send_command(self, module_name, command_name, params = None): '''Send the command from bindctl to proper module. ''' - content = [command_name] - if params: - content.append(params) - reply = {} print('b10-cmdctl send command \'%s\' to %s' %(command_name, module_name)) try: - msg = {'command' : content} + msg = isc.config.ccsession.create_command(command_name, params) self.cc.group_sendmsg(msg, module_name) #TODO, it may be blocked, msqg need to add a new interface waiting in timeout. answer, env = self.cc.group_recvmsg(False) - if answer and 'result' in answer.keys() and type(answer['result']) == list: - if answer['result'][0] != 0: - # todo: exception - print("Error: " + str(answer['result'][1])) - else: - self.update_config_data(module_name, command_name) - if (len(answer['result']) > 1): - reply = answer['result'][1] - else: - print("Error: unexpected answer from %s" % module_name) - print(answer) + if answer: + try: + rcode, arg = isc.config.ccsession.parse_answer(answer) + if rcode == 0: + self.update_config_data(module_name, command_name) + if arg != None: + return arg + else: + # todo: exception + print("Error: " + str(answer['result'][1])) + return {} + except isc.config.ccsession.ModuleCCSessionError as mcse: + print("Error in ccsession answer: %s" % str(mcse)) + print(answer) except Exception as e: print(e, ':b10-cmdctl fail send command \'%s\' to %s' % (command_name, module_name)) From 4b1e0bab42bbf8a317fd1f860a58eb85d8212211 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 25 Feb 2010 11:33:41 +0000 Subject: [PATCH 77/81] made config-specific commands also of the form of 'normal' commands, so they can be made with the same functions updated cfgmgr to use these functions git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@963 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/cpp/ccsession.cc | 42 ++++++++- src/lib/config/python/isc/config/ccsession.py | 13 ++- src/lib/config/python/isc/config/cfgmgr.py | 93 ++++++++++--------- .../config/python/isc/config/cfgmgr_test.py | 16 ++-- .../config/python/isc/config/config_data.py | 1 - .../python/isc/config/config_data_test.py | 1 - .../config/python/isc/config/module_spec.py | 1 - 7 files changed, 100 insertions(+), 67 deletions(-) diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 3810aec444..83c5e2ba01 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -103,6 +103,42 @@ parseAnswer(int &rcode, const ElementPtr msg) } } +ElementPtr +createCommand(const std::string& command, ElementPtr arg) +{ + ElementPtr cmd = Element::createFromString("{}"); + ElementPtr cmd_parts = Element::createFromString("[]"); + cmd_parts->add(Element::create(command)); + if (arg) { + cmd_parts->add(arg); + } + cmd->set("command", cmd_parts); + return cmd; +} + +/// Returns "" and empty ElementPtr() if this does not +/// look like a command +const std::string +parseCommand(ElementPtr& arg, const ElementPtr command) +{ + if (command->getType() == Element::map && + command->contains("command")) { + ElementPtr cmd = command->get("command"); + if (cmd->getType() == Element::list && + cmd->size() > 0 && + cmd->get(0)->getType() == Element::string) { + if (cmd->size() > 1) { + arg = cmd->get(1); + } else { + arg = ElementPtr(); + } + return cmd->get(0)->stringValue(); + } + } + arg = ElementPtr(); + return ""; +} + void ModuleCCSession::read_module_specification(const std::string& filename) { std::ifstream file; @@ -150,8 +186,7 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name, //session_.subscribe("Boss", "*"); //session_.subscribe("statistics", "*"); // send the data specification - ElementPtr spec_msg = Element::createFromString("{}"); - spec_msg->set("module_spec", module_specification_.getFullSpec()); + ElementPtr spec_msg = createCommand("module_spec", module_specification_.getFullSpec()); session_.group_sendmsg(spec_msg, "ConfigManager"); session_.group_recvmsg(env, answer, false); @@ -160,7 +195,6 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name, ElementPtr cmd = Element::createFromString("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }"); session_.group_sendmsg(cmd, "ConfigManager"); session_.group_recvmsg(env, answer, false); - cout << "[XX] got config: " << endl << answer->str() << endl; int rcode; ElementPtr new_config = parseAnswer(rcode, answer); handleConfigUpdate(new_config); @@ -208,7 +242,6 @@ ModuleCCSession::getSocket() int ModuleCCSession::check_command() { - cout << "[XX] check for command" << endl; ElementPtr cmd, routing, data; if (session_.group_recvmsg(routing, data, true)) { /* ignore result messages (in case we're out of sync, to prevent @@ -216,7 +249,6 @@ ModuleCCSession::check_command() if (!data->getType() == Element::map || data->contains("result")) { return 0; } - cout << "[XX] got something!" << endl << data->str() << endl; ElementPtr answer; if (data->contains("config_update")) { ElementPtr new_config = data->get("config_update"); diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index cc881e6fe7..f6ccf0feb1 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -83,6 +83,7 @@ COMMAND_GET_COMMANDS_SPEC = "get_commands_spec" COMMAND_GET_CONFIG = "get_config" COMMAND_SET_CONFIG = "set_config" COMMAND_GET_MODULE_SPEC = "get_module_spec" +COMMAND_MODULE_SPEC = "module_spec" def parse_command(msg): """Parses what may be a command message. If it looks like one, @@ -90,8 +91,11 @@ def parse_command(msg): string. If it is not, this function returns None, None""" if type(msg) == dict and len(msg.items()) == 1: cmd, value = msg.popitem() - if type(cmd) == str: - return cmd, value + if cmd == "command" and type(value) == list: + if len(value) == 1: + return value[0], None + elif len(value) > 1: + return value[0], value[1] return None, None def create_command(command_name, params = None): @@ -197,7 +201,8 @@ class ModuleCCSession(ConfigData): def __send_spec(self): """Sends the data specification to the configuration manager""" - self._session.group_sendmsg({ "module_spec": self.get_module_spec().get_full_spec() }, "ConfigManager") + msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec()) + self._session.group_sendmsg(msg, "ConfigManager") answer, env = self._session.group_recvmsg(False) def __request_config(self): @@ -289,7 +294,7 @@ class UIModuleCCSession(MultiConfigData): """Commit all local changes, send them through b10-cmdctl to the configuration manager""" if self.get_local_changes(): - self._conn.send_POST('/ConfigManager/set_config', self.get_local_changes()) + self._conn.send_POST('/ConfigManager/set_config', [ self.get_local_changes() ]) # todo: check result self.request_current_config() self.clear_local_changes() diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index d918dff999..4434d85264 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -189,10 +189,10 @@ class ConfigManager: 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: - if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': - module_name = cmd[1]['module_name'] + if cmd != None: + if type(cmd) == dict: + if 'module_name' in cmd and cmd['module_name'] != '': + module_name = cmd['module_name'] answer = isc.config.ccsession.create_answer(0, self.get_config_spec(module_name)) else: answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_module_spec command") @@ -205,10 +205,10 @@ class ConfigManager: def _handle_get_config(self, cmd): """Private function that handles the 'get_config' command""" answer = {} - if len(cmd) > 1: - if type(cmd[1]) == dict: - if 'module_name' in cmd[1] and cmd[1]['module_name'] != '': - module_name = cmd[1]['module_name'] + if cmd != None: + if type(cmd) == dict: + if 'module_name' in cmd and cmd['module_name'] != '': + module_name = cmd['module_name'] try: answer = isc.config.ccsession.create_answer(0, data.find(self.config.data, module_name)) except data.DataNotFoundError as dnfe: @@ -226,19 +226,19 @@ class ConfigManager: def _handle_set_config(self, cmd): """Private function that handles the 'set_config' command""" answer = None - if len(cmd) == 3: + if cmd == None: + return isc.config.ccsession.create_answer(1, "Wrong number of arguments") + if len(cmd) == 2: # todo: use api (and check the data against the definition?) - module_name = cmd[1] + module_name = cmd[0] conf_part = data.find_no_exc(self.config.data, module_name) - print("[XX] cfgmgr conf part:") - print(conf_part) if conf_part: - data.merge(conf_part, cmd[2]) + data.merge(conf_part, cmd[1]) self.cc.group_sendmsg({ "config_update": conf_part }, module_name) answer, env = self.cc.group_recvmsg(False) else: conf_part = data.set(self.config.data, module_name, {}) - data.merge(conf_part[module_name], cmd[2]) + data.merge(conf_part[module_name], cmd[1]) # send out changed info self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) # replace 'our' answer with that of the module @@ -246,10 +246,10 @@ class ConfigManager: rcode, val = isc.config.ccsession.parse_answer(answer) if rcode == 0: self.write_config() - elif len(cmd) == 2: + elif len(cmd) == 1: # todo: use api (and check the data against the definition?) old_data = self.config.data.copy() - data.merge(self.config.data, cmd[1]) + data.merge(self.config.data, cmd[0]) # send out changed info got_error = False err_list = [] @@ -269,6 +269,7 @@ class ConfigManager: self.config.data = old_data answer = isc.config.ccsession.create_answer(1, " ".join(err_list)) else: + print(cmd) answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") if not answer: answer = isc.config.ccsession.create_answer(1, "Error handling set_config command") @@ -285,42 +286,42 @@ class ConfigManager: # We should make one general 'spec update for module' that # passes both specification and commands at once - self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_spec() ] }, "Cmd-Ctrld") - self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands_spec() ] }, "Cmd-Ctrld") + spec_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_SPECIFICATION_UPDATE, + [ spec.get_module_name(), spec.get_config_spec() ]) + self.cc.group_sendmsg(spec_update, "Cmd-Ctrld") + cmds_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_COMMANDS_UPDATE, + [ spec.get_module_name(), spec.get_commands_spec() ]) + self.cc.group_sendmsg(cmds_update, "Cmd-Ctrld") answer = isc.config.ccsession.create_answer(0) return answer def handle_msg(self, msg): """Handle a command from the cc channel to the configuration manager""" answer = {} - if "command" in msg: - cmd = msg["command"] - try: - if cmd[0] == "get_commands_spec": - answer = isc.config.ccsession.create_answer(0, self.get_commands_spec()) - elif cmd[0] == "get_module_spec": - answer = self._handle_get_module_spec(cmd) - elif cmd[0] == "get_config": - answer = self._handle_get_config(cmd) - elif cmd[0] == "set_config": - answer = self._handle_set_config(cmd) - elif cmd[0] == "shutdown": - print("[bind-cfgd] Received shutdown command") - self.running = False - answer = isc.config.ccsession.create_answer(0) - else: - answer = isc.config.ccsession.create_answer(1, "Unknown command: " + str(cmd)) - except IndexError as ie: - answer = isc.config.ccsession.create_answer(1, "Missing argument in command: " + str(ie)) - raise ie - elif "module_spec" in msg: - try: - answer = self._handle_module_spec(isc.config.ModuleSpec(msg["module_spec"])) - except isc.config.ModuleSpecError as dde: - answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde)) - elif 'result' in msg: - # this seems wrong, might start pingpong - answer = isc.config.ccsession.create_answer(0) + #print("[XX] got msg:") + #print(msg) + cmd, arg = isc.config.ccsession.parse_command(msg) + if cmd: + #print("[XX] cmd: " + cmd) + if cmd == isc.config.ccsession.COMMAND_GET_COMMANDS_SPEC: + answer = isc.config.ccsession.create_answer(0, self.get_commands_spec()) + elif cmd == isc.config.ccsession.COMMAND_GET_MODULE_SPEC: + answer = self._handle_get_module_spec(arg) + elif cmd == isc.config.ccsession.COMMAND_GET_CONFIG: + answer = self._handle_get_config(arg) + elif cmd == isc.config.ccsession.COMMAND_SET_CONFIG: + answer = self._handle_set_config(arg) + elif cmd == "shutdown": + print("[b10-cfgmgr] Received shutdown command") + self.running = False + answer = isc.config.ccsession.create_answer(0) + elif cmd == isc.config.ccsession.COMMAND_MODULE_SPEC: + try: + answer = self._handle_module_spec(isc.config.ModuleSpec(arg)) + except isc.config.ModuleSpecError as dde: + answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde)) + else: + answer = isc.config.ccsession.create_answer(1, "Unknown command: " + str(cmd)) else: answer = isc.config.ccsession.create_answer(1, "Unknown message format: " + str(msg)) return answer diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 0e9f442a4b..28d4fcf18f 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -210,7 +210,7 @@ class TestConfigManager(unittest.TestCase): def test_handle_msg(self): self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']}) self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']}) - self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: ['badcommand']"]}) + self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]}) self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]}) self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]}) #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] }, @@ -228,7 +228,7 @@ class TestConfigManager(unittest.TestCase): {'result': [1, 'Bad module_name in get_config command']}) self._handle_msg_helper({ "command": [ "set_config" ] }, {'result': [1, 'Wrong number of arguments']}) - self._handle_msg_helper({ "command": [ "set_config", {} ] }, + self._handle_msg_helper({ "command": [ "set_config", [{}]] }, {'result': [0]}) self.assertEqual(len(self.fake_session.message_queue), 0) @@ -237,13 +237,13 @@ class TestConfigManager(unittest.TestCase): my_ok_answer = { 'result': [ 0 ] } self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") - self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 123 } ] }, + self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 123 }] ] }, my_ok_answer) self.assertEqual(len(self.fake_session.message_queue), 1) self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") self.assertEqual({'config_update': {'test': 123}}, self.fake_session.get_message(self.name, None)) - self._handle_msg_helper({ "command": [ "set_config", self.name, { "test": 124 } ] }, + self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] }, {'result': [0]}) #print(self.fake_session.message_queue) @@ -251,13 +251,11 @@ class TestConfigManager(unittest.TestCase): self.assertEqual({'config_update': {'test': 124}}, self.fake_session.get_message(self.name, None)) self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) - self._handle_msg_helper({ "module_spec": - self.spec.get_full_spec() + self._handle_msg_helper({ "command": + ["module_spec", self.spec.get_full_spec()] }, {'result': [0]}) - self._handle_msg_helper({ "module_spec": - { 'foo': 1 } - }, + self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] }, {'result': [1, 'Error in data definition: no module_name in module_spec']}) self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_config_spec() } ]}) self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]}) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 73e0828567..9f137ea4b0 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -103,7 +103,6 @@ def spec_name_list(spec, prefix="", recurse=False): for name in spec: result.append(prefix + name + "/") if recurse: - print("[XX] recurse1") result.extend(spec_name_list(spec[name],name, recurse)) elif type(spec) == list: for list_el in spec: diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 6b95a86a3e..19a8e5a879 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -29,7 +29,6 @@ class TestConfigData(unittest.TestCase): else: self.data_path = "../../../testdata" spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") - print("SPEC: " + str(type(spec))) self.cd = ConfigData(spec) #def test_module_spec_from_file(self): diff --git a/src/lib/config/python/isc/config/module_spec.py b/src/lib/config/python/isc/config/module_spec.py index b0c4ff9530..50e9acb8c9 100644 --- a/src/lib/config/python/isc/config/module_spec.py +++ b/src/lib/config/python/isc/config/module_spec.py @@ -51,7 +51,6 @@ def module_spec_from_file(spec_file, check = True): raise ModuleSpecError("Data definition has no module_spec element") result = ModuleSpec(module_spec['module_spec'], check) - print("RETURNING: " + str(type(result))) return result class ModuleSpec: From ed617e0845b4d317ffd7aabc7fd558b165e8ebce Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 25 Feb 2010 17:07:35 +0000 Subject: [PATCH 78/81] unit tests and corresponding tweaks git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@964 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/cc/python/isc/cc/data.py | 8 +- .../config/python/isc/config/config_data.py | 37 ++++---- .../python/isc/config/config_data_test.py | 91 +++++++++++++++++-- src/lib/config/testdata/data22_1.data | 3 +- src/lib/config/testdata/data22_6.data | 3 +- src/lib/config/testdata/data22_7.data | 3 +- src/lib/config/testdata/spec22.spec | 30 ++++++ 7 files changed, 139 insertions(+), 36 deletions(-) diff --git a/src/lib/cc/python/isc/cc/data.py b/src/lib/cc/python/isc/cc/data.py index dc4772e052..24c42be9a8 100644 --- a/src/lib/cc/python/isc/cc/data.py +++ b/src/lib/cc/python/isc/cc/data.py @@ -78,12 +78,8 @@ def set(element, identifier, value): if id in cur_el.keys(): cur_el = cur_el[id] else: - if value: - cur_el[id] = {} - cur_el = cur_el[id] - else: - # set to none, and parent el not found, return - return element + cur_el[id] = {} + cur_el = cur_el[id] # value can be an empty list or dict, so check for None eplicitely if value != None: cur_el[id_parts[-1]] = value diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 9f137ea4b0..6d916d17eb 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -30,10 +30,10 @@ def check_type(spec_part, value): specification part relevant for the value. Raises an isc.cc.data.DataTypeError exception if not. spec_part can be retrieved with find_spec_part()""" - if type(spec_part) == list: - data_type = "list" - else: + if type(spec_part) == dict and 'item_type' in spec_part: data_type = spec_part['item_type'] + else: + raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking")) if data_type == "integer" and type(value) != int: raise isc.cc.data.DataTypeError(str(value) + " is not an integer") @@ -62,11 +62,7 @@ def find_spec_part(element, identifier): id_parts[:] = (value for value in id_parts if value != "") cur_el = element for id in id_parts: - if type(cur_el) == dict and id in cur_el.keys(): - cur_el = cur_el[id] - elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id: - pass - elif type(cur_el) == dict and 'map_item_spec' in cur_el.keys(): + if type(cur_el) == dict and 'map_item_spec' in cur_el.keys(): found = False for cur_el_item in cur_el['map_item_spec']: if cur_el_item['item_name'] == id: @@ -83,12 +79,13 @@ def find_spec_part(element, identifier): if not found: raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el)) else: - raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el)) + raise isc.cc.data.DataNotFoundError("Not a correct config specification") return cur_el def spec_name_list(spec, prefix="", recurse=False): """Returns a full list of all possible item identifiers in the - specification (part)""" + specification (part). Raises a ConfigDataError if spec is not + a correct spec (as returned by ModuleSpec.get_config_spec()""" result = [] if prefix != "" and not prefix.endswith("/"): prefix += "/" @@ -98,7 +95,10 @@ def spec_name_list(spec, prefix="", recurse=False): name = map_el['item_name'] if map_el['item_type'] == 'map': name += "/" - result.append(prefix + name) + if recurse and 'map_item_spec' in map_el: + result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse)) + else: + result.append(prefix + name) else: for name in spec: result.append(prefix + name + "/") @@ -114,6 +114,10 @@ def spec_name_list(spec, prefix="", recurse=False): if list_el['item_type'] in ["list", "map"]: name += "/" result.append(prefix + name) + else: + raise ConfigDataError("Bad specication") + else: + raise ConfigDataError("Bad specication") return result class ConfigData: @@ -192,9 +196,9 @@ class MultiConfigData: self._local_changes = {} def set_specification(self, spec): - """Add or update a ModuleSpec""" + """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec""" if type(spec) != isc.config.ModuleSpec: - raise Exception("not a datadef: " + str(type(spec))) + raise ConfigDataError("not a datadef: " + str(type(spec))) self._specifications[spec.get_module_name()] = spec def get_module_spec(self, module): @@ -324,7 +328,6 @@ class MultiConfigData: spec = self.get_module_spec(module) if spec: spec_part = find_spec_part(spec.get_config_spec(), id) - print(spec_part) if type(spec_part) == list: for item in spec_part: entry = {} @@ -389,14 +392,14 @@ class MultiConfigData: specified, returns a list of module names. The first part of the identifier (up to the first /) is interpreted as the module name""" - if identifier: + if identifier and identifier != "/": spec = self.find_spec_part(identifier) return spec_name_list(spec, identifier + "/", recurse) else: if recurse: id_list = [] - for module in self._specifications: - id_list.extend(spec_name_list(self._specifications[module], module, recurse)) + for module in self._specifications.keys(): + id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse)) return id_list else: return list(self._specifications.keys()) diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 19a8e5a879..0c10ed4be1 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -39,8 +39,8 @@ class TestConfigData(unittest.TestCase): # self.assertRaises(ConfigDataError, ConfigData, 1) def test_check_type(self): - config_spec = self.cd.get_module_spec().get_config_spec() - spec_part = find_spec_part(config_spec, "item1") + config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec() + spec_part = find_spec_part(config_spec, "value1") check_type(spec_part, 1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) @@ -48,7 +48,7 @@ class TestConfigData(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) - spec_part = find_spec_part(config_spec, "item2") + spec_part = find_spec_part(config_spec, "value2") check_type(spec_part, 1.1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) @@ -56,7 +56,7 @@ class TestConfigData(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) - spec_part = find_spec_part(config_spec, "item3") + spec_part = find_spec_part(config_spec, "value3") check_type(spec_part, True) check_type(spec_part, False) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) @@ -65,7 +65,7 @@ class TestConfigData(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) - spec_part = find_spec_part(config_spec, "item4") + spec_part = find_spec_part(config_spec, "value4") check_type(spec_part, "asdf") self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) @@ -73,16 +73,16 @@ class TestConfigData(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) - spec_part = find_spec_part(config_spec, "item5") - check_type(spec_part, ["a", "b"]) + spec_part = find_spec_part(config_spec, "value5") + check_type(spec_part, [1, 2]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a") - self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) + self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ "a", "b" ]) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 }) - spec_part = find_spec_part(config_spec, "item6") + spec_part = find_spec_part(config_spec, "value6") check_type(spec_part, { "value1": "aaa", "value2": 2 }) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1) self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1) @@ -91,14 +91,19 @@ class TestConfigData(unittest.TestCase): self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ]) #self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "value1": 1 }) + self.assertRaises(isc.cc.data.DataTypeError, check_type, config_spec, 1) + def test_find_spec_part(self): config_spec = self.cd.get_module_spec().get_config_spec() spec_part = find_spec_part(config_spec, "item1") self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part) + spec_part = find_spec_part(config_spec, "/item1") + self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part) self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item") self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item/multilevel") + self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "item6/multilevel") + self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, 1, "item6/multilevel") spec_part = find_spec_part(config_spec, "item6/value1") - #print(spec_part) self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part) def test_spec_name_list(self): @@ -106,7 +111,29 @@ class TestConfigData(unittest.TestCase): self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list) name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True) self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list) + spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6") + name_list = spec_name_list(spec_part, "item6", True) + self.assertEqual(['item6/value1', 'item6/value2'], name_list) + spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6") + name_list = spec_name_list(spec_part, "item6", True) + self.assertEqual(['item6/value1', 'item6/value2'], name_list) + config_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec22.spec").get_config_spec() + spec_part = find_spec_part(config_spec, "value9") + name_list = spec_name_list(spec_part, "value9", True) + self.assertEqual(['value9/v91', 'value9/v92/v92a', 'value9/v92/v92b'], name_list) + + name_list = spec_name_list({ "myModule": config_spec }, "", False) + self.assertEqual(['myModule/'], name_list) + name_list = spec_name_list({ "myModule": config_spec }, "", True) + self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5/', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7/', 'myModule/value8/', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list) + + self.assertRaises(ConfigDataError, spec_name_list, 1) + self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ]) + + def test_init(self): + self.assertRaises(ConfigDataError, ConfigData, "asdf") + def test_get_value(self): value, default = self.cd.get_value("item1") self.assertEqual(1, value) @@ -178,6 +205,7 @@ class TestMultiConfigData(unittest.TestCase): self.mcd.set_specification(module_spec) self.assert_(module_spec.get_module_name() in self.mcd._specifications) self.assertEquals(module_spec, self.mcd._specifications[module_spec.get_module_name()]) + self.assertRaises(ConfigDataError, self.mcd.set_specification, "asdf") def test_get_module_spec(self): module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") @@ -190,11 +218,18 @@ class TestMultiConfigData(unittest.TestCase): def test_find_spec_part(self): spec_part = self.mcd.find_spec_part("Spec2/item1") self.assertEqual(None, spec_part) + spec_part = self.mcd.find_spec_part("/Spec2/item1") + self.assertEqual(None, spec_part) module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") self.mcd.set_specification(module_spec) spec_part = self.mcd.find_spec_part("Spec2/item1") self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part) + def test_get_current_config(self): + cf = { 'module1': { 'item1': 2, 'item2': True } } + self.mcd._set_current_config(cf); + self.assertEqual(cf, self.mcd.get_current_config()) + def test_get_local_changes(self): module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") self.mcd.set_specification(module_spec) @@ -234,6 +269,8 @@ class TestMultiConfigData(unittest.TestCase): self.mcd.set_specification(module_spec) value = self.mcd.get_default_value("Spec2/item1") self.assertEqual(1, value) + value = self.mcd.get_default_value("/Spec2/item1") + self.assertEqual(1, value) value = self.mcd.get_default_value("Spec2/item6/value1") self.assertEqual('default', value) value = self.mcd.get_default_value("Spec2/item6/value2") @@ -264,6 +301,34 @@ class TestMultiConfigData(unittest.TestCase): self.mcd.set_specification(module_spec) maps = self.mcd.get_value_maps() self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec2', 'value': None, 'modified': False}], maps) + self.mcd._set_current_config({ "Spec2": { "item1": 2 } }) + self.mcd.set_value("Spec2/item3", False) + maps = self.mcd.get_value_maps("/Spec2") + self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}, + {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}, + {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}, + {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}, + {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False}, + {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps) + maps = self.mcd.get_value_maps("Spec2") + self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}, + {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}, + {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}, + {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}, + {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False}, + {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps) + maps = self.mcd.get_value_maps("/Spec2/item5") + self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False}, + {'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps) + maps = self.mcd.get_value_maps("/Spec2/item1") + self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps) + maps = self.mcd.get_value_maps("/Spec2/item2") + self.assertEqual([{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps) + maps = self.mcd.get_value_maps("/Spec2/item3") + self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps) + maps = self.mcd.get_value_maps("/Spec2/item4") + self.assertEqual([{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps) + def test_set_value(self): module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") @@ -279,6 +344,12 @@ class TestMultiConfigData(unittest.TestCase): self.mcd.set_specification(module_spec) config_items = self.mcd.get_config_item_list() self.assertEqual(['Spec2'], config_items) + config_items = self.mcd.get_config_item_list(None, False) + self.assertEqual(['Spec2'], config_items) + config_items = self.mcd.get_config_item_list(None, True) + self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items) + config_items = self.mcd.get_config_item_list("Spec2", True) + self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items) config_items = self.mcd.get_config_item_list("Spec2") self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items) config_items = self.mcd.get_config_item_list("Spec2", True) diff --git a/src/lib/config/testdata/data22_1.data b/src/lib/config/testdata/data22_1.data index 9843a8d248..b8ea09b22a 100644 --- a/src/lib/config/testdata/data22_1.data +++ b/src/lib/config/testdata/data22_1.data @@ -4,5 +4,6 @@ "value3": True, "value4": "foo", "value5": [ 1, 2, 3 ], - "value6": { "v61": "bar", "v62": True } + "value6": { "v61": "bar", "v62": True }, + "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } } } diff --git a/src/lib/config/testdata/data22_6.data b/src/lib/config/testdata/data22_6.data index d513671261..97c01bda6e 100644 --- a/src/lib/config/testdata/data22_6.data +++ b/src/lib/config/testdata/data22_6.data @@ -5,5 +5,6 @@ "value4": "foo", "value5": [ 1, 2, 3 ], "value6": { "v61": "bar", "v62": True }, - "value7": [ 1, 2.2, "str", True ] + "value7": [ 1, 2.2, "str", True ], + "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } } } diff --git a/src/lib/config/testdata/data22_7.data b/src/lib/config/testdata/data22_7.data index 416d8efd39..c6eee967c9 100644 --- a/src/lib/config/testdata/data22_7.data +++ b/src/lib/config/testdata/data22_7.data @@ -5,5 +5,6 @@ "value4": "foo", "value5": [ 1, 2, 3 ], "value6": { "v61": "bar", "v62": True }, - "value8": [ { "a": "d" }, { "a": "e" } ] + "value8": [ { "a": "d" }, { "a": "e" } ], + "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } } } diff --git a/src/lib/config/testdata/spec22.spec b/src/lib/config/testdata/spec22.spec index 5e9cc5654d..4a435e6ed7 100644 --- a/src/lib/config/testdata/spec22.spec +++ b/src/lib/config/testdata/spec22.spec @@ -78,6 +78,36 @@ ] } }, + { "item_name": "value9", + "item_type": "map", + "item_optional": False, + "item_default": {}, + "map_item_spec": [ + { "item_name": "v91", + "item_type": "string", + "item_optional": False, + "item_default": "def" + }, + { "item_name": "v92", + "item_type": "map", + "item_optional": False, + "item_default": {}, + "map_item_spec": [ + { "item_name": "v92a", + "item_type": "string", + "item_optional": False, + "item_default": "Hello" + } , + { + "item_name": "v92b", + "item_type": "integer", + "item_optional": False, + "item_default": 47806 + } + ] + } + ] + } ] } } From 90750f86abe4908b27b174165021299d08b18f65 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Thu, 25 Feb 2010 17:35:24 +0000 Subject: [PATCH 79/81] Remove unused, old code. (Makefile had it commented out and okayed by each@.) git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@966 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/auth/cpp/Makefile.am | 1 - src/lib/auth/cpp/data_source_plot.cc | 187 --------------------------- src/lib/auth/cpp/data_source_plot.h | 96 -------------- 3 files changed, 284 deletions(-) delete mode 100644 src/lib/auth/cpp/data_source_plot.cc delete mode 100644 src/lib/auth/cpp/data_source_plot.h diff --git a/src/lib/auth/cpp/Makefile.am b/src/lib/auth/cpp/Makefile.am index 25f055552d..1f36c33ffd 100644 --- a/src/lib/auth/cpp/Makefile.am +++ b/src/lib/auth/cpp/Makefile.am @@ -5,5 +5,4 @@ CLEANFILES = *.gcno *.gcda lib_LTLIBRARIES = libauth.la libauth_la_SOURCES = data_source.h data_source.cc libauth_la_SOURCES += data_source_static.h data_source_static.cc -#libauth_la_SOURCES += data_source_plot.h data_source_plot.cc libauth_la_SOURCES += query.h query.cc diff --git a/src/lib/auth/cpp/data_source_plot.cc b/src/lib/auth/cpp/data_source_plot.cc deleted file mode 100644 index fab4e38758..0000000000 --- a/src/lib/auth/cpp/data_source_plot.cc +++ /dev/null @@ -1,187 +0,0 @@ - -#include - -#include - -#include "data_source_plot.h" - -namespace isc { -namespace dns { - -// this implementation returns fixed records, -// and does not allow update statements - -using namespace isc::dns; -using namespace isc::dns::rdata; -using namespace isc::data; - -namespace { -const Name authors_name("authors.bind"); -const Name version_name("version.bind"); -} - -void -DataSourceParkingLot::serve(std::string zone_name) { - zones.serve(zone_name); -} - -void -DataSourceParkingLot::addARecord(std::string data) { - a_records.push_back(RdataPtr(new in::A(data))); -} - -void -DataSourceParkingLot::addAAAARecord(std::string data) { - aaaa_records.push_back(RdataPtr(new in::AAAA(data))); -} - -void -DataSourceParkingLot::addNSRecord(std::string data) { - ns_records.push_back(RdataPtr(new generic::NS(data))); -} - -void -DataSourceParkingLot::setSOARecord(RdataPtr soa_record) { -} - -void -DataSourceParkingLot::setDefaultZoneData() { - clearARecords(); - clearAAAARecords(); - clearNSRecords(); - - addARecord("127.0.0.1"); - addAAAARecord("::1"); - addNSRecord("ns1.parking.example"); - addNSRecord("ns2.parking.example"); - addNSRecord("ns3.parking.example"); -} - -DataSourceParkingLot::DataSourceParkingLot() { - setDefaultZoneData(); - soa = RdataPtr(new generic::SOA(Name("parking.example"), - Name("noc.parking.example"), - 1, 1800, 900, 604800, 86400)); -} - -bool -DataSourceParkingLot::hasZoneFor(const Name& name, Name &zone_name) -{ - if (name == authors_name) { - zone_name = authors_name; - return (true); - } else if (name == version_name) { - zone_name = version_name; - return (true); - } - return zones.findClosest(name, zone_name); -} - -SearchResult -DataSourceParkingLot::findRRsets(const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const -{ - SearchResult result; - - if (clas == RRClass::CH()) { - if (type == RRType::TXT()) { - if (name == authors_name) { - RRsetPtr rrset = RRsetPtr(new RRset(authors_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - rrset->addRdata(generic::TXT("Han Feng")); - rrset->addRdata(generic::TXT("Kazunori Fujiwara")); - rrset->addRdata(generic::TXT("Michael Graff")); - rrset->addRdata(generic::TXT("Evan Hunt")); - rrset->addRdata(generic::TXT("Jelte Jansen")); - rrset->addRdata(generic::TXT("Jin Jian")); - rrset->addRdata(generic::TXT("JINMEI Tatuya")); - rrset->addRdata(generic::TXT("Naoki Kambe")); - rrset->addRdata(generic::TXT("Shane Kerr")); - rrset->addRdata(generic::TXT("Zhang Likun")); - rrset->addRdata(generic::TXT("Jeremy C. Reed")); - - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else if (name == version_name) { - RRsetPtr rrset = RRsetPtr(new RRset(version_name, RRClass::CH(), - RRType::TXT(), RRTTL(0))); - rrset->addRdata(generic::TXT("BIND10 0.0.1")); - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else { - result.setStatus(SearchResult::name_not_found); - } - } else if (type == RRType::NS()) { - if (name == authors_name || name == version_name) { - RRsetPtr rrset = RRsetPtr(new RRset(name, RRClass::CH(), - RRType::NS(), - RRTTL(0))); - rrset->addRdata(generic::NS(name)); - result.addRRset(rrset); - result.setStatus(SearchResult::success); - } else { - result.setStatus(SearchResult::name_not_found); - } - } else { - result.setStatus(SearchResult::name_not_found); - } - } else if (clas == RRClass::IN()) { - if (zones.contains(name)) { - RRsetPtr rrset = RRsetPtr(new RRset(name, clas, type, RRTTL(3600))); - result.setStatus(SearchResult::success); - if (type == RRType::A()) { - BOOST_FOREACH(RdataPtr a, a_records) { - rrset->addRdata(a); - } - } else if (type == RRType::AAAA()) { - BOOST_FOREACH(RdataPtr aaaa, aaaa_records) { - rrset->addRdata(aaaa); - } - } else if (type == RRType::NS()) { - BOOST_FOREACH(RdataPtr ns, ns_records) { - rrset->addRdata(ns); - } - } else if (type == RRType::SOA()) { - rrset->addRdata(soa); - } - result.addRRset(rrset); - } else { - // we don't have the name itself. Do we have the zone? - if (zones.contains(zone_name)) { - result.setStatus(SearchResult::name_not_found); - } else { - result.setStatus(SearchResult::zone_not_found); - } - } - } else { - result.setStatus(SearchResult::zone_not_found); - } - return result; -} - -/// Do direct 'search' in database, no extra processing, -/// and add the resulting rrsets to the specified section -/// in the given message -/// returns the status code of the searchresult -/// Once the dns logic is moved from parkinglot to this class, -/// we should probably make this private -SearchResult::status_type -DataSourceParkingLot::addToMessage(Message& msg, - const Section& section, - const Name& zone_name, - const Name& name, - const RRClass& clas, - const RRType& type) const -{ - SearchResult result = findRRsets(zone_name, name, clas, type); - BOOST_FOREACH(RRsetPtr rrset, result) { - msg.addRRset(section, rrset); - } - return result.getStatus(); -} - - -} -} diff --git a/src/lib/auth/cpp/data_source_plot.h b/src/lib/auth/cpp/data_source_plot.h deleted file mode 100644 index 69b21256df..0000000000 --- a/src/lib/auth/cpp/data_source_plot.h +++ /dev/null @@ -1,96 +0,0 @@ - -// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC") -// -// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL ISC 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. - -// $Id$ - -#ifndef __DATA_SOURCE_SIMPLE_H -#define __DATA_SOURCE_SIMPLE_H - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "common.h" - -#include "data_source.h" - -#include "zoneset.h" - -namespace isc { -namespace dns { - -class DataSourceParkingLot : public DataSource { -public: - DataSourceParkingLot(); - - void init() {}; - void close() {}; - bool hasZoneFor(const Name& name, Name &zone_name); - SearchResult findRRsets(const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const; - - /* move these to private (or to zoneset) and the calling functions - * from parkinglot to here? */ - void serve(std::string zone_name); - void clear_zones() { zones.clear_zones(); }; - - void clearARecords() { a_records.clear(); }; - void clearAAAARecords() { aaaa_records.clear(); }; - void clearNSRecords() { ns_records.clear(); }; - - void addARecord(std::string data); - void addAAAARecord(std::string data); - void addNSRecord(std::string data); - - void setSOARecord(isc::dns::rdata::RdataPtr soa_record); - - /// Do direct 'search' in database, no extra processing, - /// and add the resulting rrsets to the specified section - /// in the given message - /// Once the dns logic is moved from parkinglot to this class, - /// we should probably make this private - SearchResult::status_type addToMessage(Message& msg, - const isc::dns::Section& section, - const isc::dns::Name& zone_name, - const isc::dns::Name& name, - const isc::dns::RRClass& clas, - const isc::dns::RRType& type) const; - -private: - // - void setDefaultZoneData(); - - std::vector a_records, aaaa_records, ns_records; - isc::dns::rdata::RdataPtr soa; - ZoneSet zones; - -}; - -} -} - -#endif - -// Local Variables: -// mode: c++ -// End: From 25a2bfcf77871629230d0f934dc5ab966c960645 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 25 Feb 2010 17:49:39 +0000 Subject: [PATCH 80/81] ok this is as far as i can get with coverage on config_data.py git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@967 e5f2f494-b856-4b98-b285-d166d9295462 --- .../config/python/isc/config/config_data.py | 15 ++++++++---- .../python/isc/config/config_data_test.py | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index 6d916d17eb..8a2472257a 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -73,7 +73,7 @@ def find_spec_part(element, identifier): elif type(cur_el) == list: found = False for cur_el_item in cur_el: - if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys(): + if cur_el_item['item_name'] == id: cur_el = cur_el_item found = True if not found: @@ -201,6 +201,11 @@ class MultiConfigData: raise ConfigDataError("not a datadef: " + str(type(spec))) self._specifications[spec.get_module_name()] = spec + def remove_specification(self, module_name): + """Removes the specification with the given module name. Does nothing if it wasn't there.""" + if module_name in self._specifications: + del self._specifications[module_name] + def get_module_spec(self, module): """Returns the ModuleSpec for the module with the given name. If there is no such module, it returns None""" @@ -344,13 +349,13 @@ class MultiConfigData: else: entry['default'] = False result.append(entry) - else: + elif type(spec_part) == dict: item = spec_part if item['item_type'] == 'list': li_spec = item['list_item_spec'] - l, status = self.get_value("/" + identifier) - if l: - for value in l: + item_list, status = self.get_value("/" + identifier) + if item_list != None: + for value in item_list: result_part2 = {} result_part2['name'] = li_spec['item_name'] result_part2['value'] = value diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index 0c10ed4be1..20fa19dd72 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/src/lib/config/python/isc/config/config_data_test.py @@ -297,6 +297,20 @@ class TestMultiConfigData(unittest.TestCase): self.assertEqual(MultiConfigData.NONE, status) def test_get_value_maps(self): + maps = self.mcd.get_value_maps() + self.assertEqual([], maps) + + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec") + self.mcd.set_specification(module_spec) + maps = self.mcd.get_value_maps() + self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec1', 'value': None, 'modified': False}], maps) + maps = self.mcd.get_value_maps('Spec2') + self.assertEqual([], maps) + maps = self.mcd.get_value_maps('Spec1') + self.assertEqual([], maps) + self.mcd.remove_specification("Spec1") + self.mcd.remove_specification("foo") + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") self.mcd.set_specification(module_spec) maps = self.mcd.get_value_maps() @@ -329,6 +343,15 @@ class TestMultiConfigData(unittest.TestCase): maps = self.mcd.get_value_maps("/Spec2/item4") self.assertEqual([{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps) + module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec") + self.mcd.set_specification(module_spec) + maps = self.mcd.get_value_maps("/Spec24/item") + self.assertEqual([], maps) + self.mcd._set_current_config({ "Spec24": { "item": [] } }) + maps = self.mcd.get_value_maps("/Spec24/item") + self.assertEqual([], maps) + + def test_set_value(self): module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec") From 91847ce76d388cdf535ba4813c0209b5f26c18c0 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 25 Feb 2010 22:13:05 +0000 Subject: [PATCH 81/81] oh no more unittests git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@970 e5f2f494-b856-4b98-b285-d166d9295462 --- src/lib/config/python/isc/config/ccsession.py | 2 + src/lib/config/python/isc/config/cfgmgr.py | 35 +++++--- .../config/python/isc/config/cfgmgr_test.py | 90 +++++++++++++++++-- src/lib/config/testdata/b10-config.db | 2 +- 4 files changed, 109 insertions(+), 20 deletions(-) diff --git a/src/lib/config/python/isc/config/ccsession.py b/src/lib/config/python/isc/config/ccsession.py index f6ccf0feb1..7c0f78c6e7 100644 --- a/src/lib/config/python/isc/config/ccsession.py +++ b/src/lib/config/python/isc/config/ccsession.py @@ -46,6 +46,8 @@ def parse_answer(msg): """Returns a tuple (rcode, value), where value depends on the command that was called. If rcode != 0, value is a string containing an error message""" + if type(msg) != dict: + raise ModuleCCSessionError("Answer message is not a dict: " + str(msg)) if 'result' not in msg: raise ModuleCCSessionError("answer message does not contain 'result' element") elif type(msg['result']) != list: diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index 4434d85264..66cdcddb13 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -91,9 +91,16 @@ class ConfigManagerData: file.write(s) file.write("\n") file.close() - os.rename(tmp_filename, self.db_filename) + if output_file_name: + os.rename(tmp_filename, output_file_name) + else: + os.rename(tmp_filename, self.db_filename) except IOError as ioe: - print("Unable to write config file; configuration not stored") + # TODO: log this (level critical) + print("[b10-cfgmgr] Unable to write config file; configuration not stored: " + str(ioe)) + except OSError as ose: + # TODO: log this (level critical) + print("[b10-cfgmgr] Unable to write config file; configuration not stored: " + str(ose)) def __eq__(self, other): """Returns True if the data contained is equal. data_path and @@ -153,7 +160,7 @@ class ConfigManager: config_data = {} if name: if name in self.module_specs: - config_data[name] = self.module_specs[name].get_data + config_data[name] = self.module_specs[name].get_config_spec() else: for module_name in self.module_specs.keys(): config_data[module_name] = self.module_specs[module_name].get_config_spec() @@ -166,7 +173,7 @@ class ConfigManager: commands = {} if name: if name in self.module_specs: - commands[name] = self.module_specs[name].get_commands_spec + commands[name] = self.module_specs[name].get_commands_spec() else: for module_name in self.module_specs.keys(): commands[module_name] = self.module_specs[module_name].get_commands_spec() @@ -243,9 +250,10 @@ class ConfigManager: self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name) # replace 'our' answer with that of the module answer, env = self.cc.group_recvmsg(False) - rcode, val = isc.config.ccsession.parse_answer(answer) - if rcode == 0: - self.write_config() + if answer: + rcode, val = isc.config.ccsession.parse_answer(answer) + if rcode == 0: + self.write_config() elif len(cmd) == 1: # todo: use api (and check the data against the definition?) old_data = self.config.data.copy() @@ -257,10 +265,14 @@ class ConfigManager: if module != "version": self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module) answer, env = self.cc.group_recvmsg(False) - rcode, val = isc.config.ccsession.parse_answer(answer) - if rcode != 0: + if answer == None: got_error = True - err_list.append(val) + err_list.append("No answer message from " + module) + else: + rcode, val = isc.config.ccsession.parse_answer(answer) + if rcode != 0: + got_error = True + err_list.append(val) if not got_error: self.write_config() answer = isc.config.ccsession.create_answer(0) @@ -272,7 +284,7 @@ class ConfigManager: print(cmd) answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") if not answer: - answer = isc.config.ccsession.create_answer(1, "Error handling set_config command") + answer = isc.config.ccsession.create_answer(1, "No answer message from " + cmd[0]) return answer @@ -312,6 +324,7 @@ class ConfigManager: elif cmd == isc.config.ccsession.COMMAND_SET_CONFIG: answer = self._handle_set_config(arg) elif cmd == "shutdown": + # TODO: logging print("[b10-cfgmgr] Received shutdown command") self.running = False answer = isc.config.ccsession.create_answer(0) diff --git a/src/lib/config/python/isc/config/cfgmgr_test.py b/src/lib/config/python/isc/config/cfgmgr_test.py index 28d4fcf18f..b1b93d9381 100644 --- a/src/lib/config/python/isc/config/cfgmgr_test.py +++ b/src/lib/config/python/isc/config/cfgmgr_test.py @@ -99,6 +99,9 @@ class FakeModuleCCSession: def group_sendmsg(self, msg, channel, target = None): self.message_queue.append([ channel, target, msg ]) + def group_reply(self, env, msg): + pass + def group_recvmsg(self, blocking): for qm in self.message_queue: if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in self.subscriptions[qm[0]]): @@ -176,6 +179,9 @@ class TestConfigManager(unittest.TestCase): 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()) + config_spec = self.cm.get_config_spec('Spec2') + self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec()) + def test_get_commands_spec(self): commands_spec = self.cm.get_commands_spec() @@ -193,11 +199,17 @@ class TestConfigManager(unittest.TestCase): 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()) + commands_spec = self.cm.get_commands_spec('Spec2') + 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}) + # due to what get written, the value here is what the last set_config command in test_handle_msg does + self.assertEqual(self.cm.config.data, {'TestModule': {'test': 125}, 'version': 1}) + self.cm.data_path = "/no_such_path" + self.cm.read_config() + self.assertEqual(self.cm.config.data, {'version': 1}) def test_write_config(self): # tested in ConfigManagerData tests @@ -213,6 +225,7 @@ class TestConfigManager(unittest.TestCase): self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]}) self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]}) self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]}) + self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "Spec2" } ] }, { 'result': [ 0, {} ]}) #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] }, # {'result': [1, 'No specification for module nosuchmodule']}) self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] }, @@ -236,21 +249,77 @@ class TestConfigManager(unittest.TestCase): # those in our fake msgq first. my_ok_answer = { 'result': [ 0 ] } + + # Send the 'ok' that cfgmgr expects back to the fake queue first self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") + # then send the command self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 123 }] ] }, my_ok_answer) + # The cfgmgr should have eaten the ok message, and sent out an update again self.assertEqual(len(self.fake_session.message_queue), 1) - self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") self.assertEqual({'config_update': {'test': 123}}, self.fake_session.get_message(self.name, None)) - self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] }, - {'result': [0]}) + # and the queue should now be empty again + self.assertEqual(len(self.fake_session.message_queue), 0) - #print(self.fake_session.message_queue) + # below are variations of the theme above + self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") + self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] }, + my_ok_answer) self.assertEqual(len(self.fake_session.message_queue), 1) self.assertEqual({'config_update': {'test': 124}}, self.fake_session.get_message(self.name, None)) - self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) + self.assertEqual(len(self.fake_session.message_queue), 0) + + + # This is the last 'succes' one, the value set here is what test_read_config expects + self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager") + self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] }, + my_ok_answer ) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 125}}, + self.fake_session.get_message(self.name, None)) + self.assertEqual(len(self.fake_session.message_queue), 0) + + self.fake_session.group_sendmsg({ 'result': "bad_answer" }, "ConfigManager") + self.assertRaises(isc.config.ccsession.ModuleCCSessionError, + self.cm.handle_msg, + { "command": [ "set_config", [ { self.name: { "test": 125 } }] ] } ) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 125}}, + self.fake_session.get_message(self.name, None)) + self.assertEqual(len(self.fake_session.message_queue), 0) + + my_bad_answer = { 'result': [1, "bad_answer"] } + self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager") + self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] }, + my_bad_answer ) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 125}}, + self.fake_session.get_message(self.name, None)) + self.assertEqual(len(self.fake_session.message_queue), 0) + + my_bad_answer = { 'result': [1, "bad_answer"] } + self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager") + self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] }, + my_bad_answer ) + self.assertEqual(len(self.fake_session.message_queue), 1) + self.assertEqual({'config_update': {'test': 125}}, + self.fake_session.get_message(self.name, None)) + self.assertEqual(len(self.fake_session.message_queue), 0) + + self._handle_msg_helper({ "command": [ "set_config", [ ] ] }, + {'result': [1, 'Wrong number of arguments']} ) + self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] }, + { 'result': [1, 'No answer message from TestModule']} ) + self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] }, + { 'result': [1, 'No answer message from TestModule']} ) + + #self.assertEqual(len(self.fake_session.message_queue), 1) + #self.assertEqual({'config_update': {'test': 124}}, + # self.fake_session.get_message(self.name, None)) + #self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data) + # self._handle_msg_helper({ "command": ["module_spec", self.spec.get_full_spec()] }, @@ -267,10 +336,15 @@ class TestConfigManager(unittest.TestCase): # self.fake_session.get_message("Cmd-Ctrld", None)) #self.assertEqual({'commands_update': [ self.name, self.commands ] }, # self.fake_session.get_message("Cmd-Ctrld", None)) - - + + self._handle_msg_helper({ "command": + ["shutdown"] + }, + {'result': [0]}) def test_run(self): + self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager") + self.cm.run() pass diff --git a/src/lib/config/testdata/b10-config.db b/src/lib/config/testdata/b10-config.db index 2a07bbae3c..97b3035d44 100644 --- a/src/lib/config/testdata/b10-config.db +++ b/src/lib/config/testdata/b10-config.db @@ -1 +1 @@ -{'version': 1} +{'TestModule': {'test': 125}, 'version': 1}