diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in index 4bde11af50b62eb177972156e1603cba910b92be..98d7005974502a018f29896f540fced97c07193e 100644 --- a/src/bin/auth/auth.spec.pre.in +++ b/src/bin/auth/auth.spec.pre.in @@ -1,6 +1,7 @@ { "module_spec": { "module_name": "Auth", + "module_description": "Authoritative service", "config_data": [ { "item_name": "database_file", "item_type": "string", diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec index 796bea043f170fe3aa79934552d409265847bec8..01c0f94ca4cc27426f81e55029417399f8976619 100644 --- a/src/bin/bind10/bob.spec +++ b/src/bin/bind10/bob.spec @@ -1,6 +1,7 @@ { "module_spec": { "module_name": "Boss", + "module_description": "Master process", "config_data": [ { "item_name": "example_string", diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index e13d673d958835550d9e821cf312fb6873484e1f..9d2a76e2cc5ca0feabb9fffa7a0a0f466c254af8 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -180,13 +180,9 @@ class BindCmdInterpreter(Cmd): def _update_commands(self): - '''Get the commands of all modules. ''' - cmd_spec = self.send_GET('/command_spec') - if not cmd_spec: - return - - for module_name in cmd_spec.keys(): - self._prepare_module_commands(module_name, cmd_spec[module_name]) + '''Update the commands of all modules. ''' + for module_name in self.config_data.get_config_item_list(): + self._prepare_module_commands(self.config_data.get_module_spec(module_name)) def send_GET(self, url, body = None): '''Send GET request to cmdctl, session id is send with the name @@ -222,11 +218,11 @@ 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_spec): '''Prepare the module commands''' - module = ModuleInfo(name = module_name, - desc = "same here") - for command in module_commands: + module = ModuleInfo(name = module_spec.get_module_name(), + desc = module_spec.get_module_description()) + for command in module_spec.get_commands_spec(): cmd = CommandInfo(name = command["command_name"], desc = command["command_description"]) for arg in command["command_args"]: diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in index 8e845f69c2e21a0630b188d5c452e315510f2644..489f9269466b40b1bac3c13f210c7e6618a17685 100644 --- a/src/bin/cmdctl/cmdctl.py.in +++ b/src/bin/cmdctl/cmdctl.py.in @@ -219,8 +219,7 @@ class CommandControl(): self._verbose = verbose self.cc = isc.cc.Session() self.cc.group_subscribe('Cmd-Ctrld') - self.command_spec = self.get_cmd_specification() - self.config_spec = self.get_data_specification() + self.module_spec = self.get_module_specification() self.config_data = self.get_config_data() def _parse_command_result(self, rcode, reply): @@ -229,10 +228,6 @@ class CommandControl(): return {} return reply - def get_cmd_specification(self): - rcode, reply = self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_COMMANDS_SPEC) - return self._parse_command_result(rcode, reply) - def get_config_data(self): '''Get config data for all modules from configmanager ''' rcode, reply = self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_CONFIG) @@ -244,7 +239,7 @@ class CommandControl(): 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): + def get_module_specification(self): rcode, reply = self.send_command('ConfigManager', isc.config.ccsession.COMMAND_GET_MODULE_SPEC) return self._parse_command_result(rcode, reply) @@ -253,10 +248,8 @@ class CommandControl(): (message, env) = self.cc.group_recvmsg(True) 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] + if command == isc.config.ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE: + self.module_spec[arg[0]] = arg[1] elif command == isc.config.ccsession.COMMAND_SHUTDOWN: return False (message, env) = self.cc.group_recvmsg(True) @@ -278,11 +271,12 @@ class CommandControl(): if module_name == 'ConfigManager': return self.send_command(module_name, command_name, params) - if module_name not in self.command_spec.keys(): + if module_name not in self.module_spec.keys(): return 1, {'error' : 'unknown module'} cmd_valid = False - commands = self.command_spec[module_name] + # todo: make validate_command() in ModuleSpec class + commands = self.module_spec[module_name]["commands"] for cmd in commands: if cmd['command_name'] == command_name: cmd_valid = True @@ -383,12 +377,10 @@ class SecureHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): '''Currently only support the following three url GET request ''' rcode, reply = http.client.NO_CONTENT, [] if not module: - if id == 'command_spec': - rcode, reply = http.client.OK, self.cmdctrl.command_spec - elif id == 'config_data': + if id == 'config_data': rcode, reply = http.client.OK, self.cmdctrl.config_data - elif id == 'config_spec': - rcode, reply = http.client.OK, self.cmdctrl.config_spec + elif id == 'module_spec': + rcode, reply = http.client.OK, self.cmdctrl.module_spec return rcode, reply diff --git a/src/bin/cmdctl/cmdctl.spec b/src/bin/cmdctl/cmdctl.spec index e4379736e691edc24b945a6692c57185f248671d..4b28a2a9ef336718c093edce9cc65a271471d803 100644 --- a/src/bin/cmdctl/cmdctl.spec +++ b/src/bin/cmdctl/cmdctl.spec @@ -1,6 +1,7 @@ { "module_spec": { "module_name": "Cmdctl", + "module_description": "Interface for command and control", "config_data": [ { "item_name": "key_file", diff --git a/src/bin/xfrin/xfrin.spec.pre.in b/src/bin/xfrin/xfrin.spec.pre.in index 5efbda76ff52c7f662ccba3cfb29e6dd2513fe36..97099ed823cdf94560fe58364f013c52feb92799 100644 --- a/src/bin/xfrin/xfrin.spec.pre.in +++ b/src/bin/xfrin/xfrin.spec.pre.in @@ -1,6 +1,7 @@ { "module_spec": { "module_name": "Xfrin", + "module_description": "XFR in daemon", "config_data": [ { "item_name": "transfers_in", diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am index 05b19db4e6e00d6e83ddcd1e0d614832ad2fce69..b2486dde19a1f976e370e1bce28e16fbab9e7e1e 100644 --- a/src/lib/config/Makefile.am +++ b/src/lib/config/Makefile.am @@ -46,3 +46,5 @@ EXTRA_DIST += testdata/spec21.spec EXTRA_DIST += testdata/spec22.spec EXTRA_DIST += testdata/spec23.spec EXTRA_DIST += testdata/spec24.spec +EXTRA_DIST += testdata/spec25.spec +EXTRA_DIST += testdata/spec26.spec diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc index dc38ca2f4d8cbfdff197f872a84a061a2966eb54..c6bf51b16fc8482a8132ddadc1f057daa52d7bc9 100644 --- a/src/lib/config/module_spec.cc +++ b/src/lib/config/module_spec.cc @@ -145,6 +145,7 @@ check_command_list(const ElementPtr& spec) { static void check_data_specification(const ElementPtr& spec) { check_leaf_item(spec, "module_name", Element::string, true); + check_leaf_item(spec, "module_description", Element::string, false); // config_data is not mandatory; module could just define // commands and have no config if (spec->contains("config_data")) { @@ -204,6 +205,16 @@ ModuleSpec::getModuleName() const return module_specification->get("module_name")->stringValue(); } +const std::string +ModuleSpec::getModuleDescription() const +{ + if (module_specification->contains("module_description")) { + return module_specification->get("module_description")->stringValue(); + } else { + return std::string(""); + } +} + bool ModuleSpec::validate_config(const ElementPtr data, const bool full) { diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h index 908eef91206743ec40cf81468d7e207eba97fc4f..948670ad3cb15702c26abc884e9f08effd0d4e1f 100644 --- a/src/lib/config/module_spec.h +++ b/src/lib/config/module_spec.h @@ -77,6 +77,10 @@ namespace isc { namespace config { /// Returns the module name as specified by the specification const std::string getModuleName() const; + /// Returns the module description as specified by the specification + /// returns an empty string if there is no description + const std::string getModuleDescription() const; + // returns true if the given element conforms to this data // configuration specification /// Validates the given configuration data for this specification. diff --git a/src/lib/config/testdata/spec25.spec b/src/lib/config/testdata/spec25.spec new file mode 100644 index 0000000000000000000000000000000000000000..6a174d5356741afb2f8e88ee6d6d5bbdddda74d1 --- /dev/null +++ b/src/lib/config/testdata/spec25.spec @@ -0,0 +1,7 @@ +{ + "module_spec": { + "module_name": "Spec25", + "module_description": "Just an empty module" + } +} + diff --git a/src/lib/config/testdata/spec26.spec b/src/lib/config/testdata/spec26.spec new file mode 100644 index 0000000000000000000000000000000000000000..27f3c5b4de43f61c2f252232e2c8f63133a6758d --- /dev/null +++ b/src/lib/config/testdata/spec26.spec @@ -0,0 +1,6 @@ +{ + "module_spec": { + "module_name": "Spec26", + "module_description": 1 + } +} diff --git a/src/lib/config/tests/module_spec_unittests.cc b/src/lib/config/tests/module_spec_unittests.cc index fee7300b22cee66783c05afdce41ff19cdbf377c..658c46cedb8a684a1c76901aa744c950bc8b8b16 100644 --- a/src/lib/config/tests/module_spec_unittests.cc +++ b/src/lib/config/tests/module_spec_unittests.cc @@ -60,6 +60,12 @@ TEST(ModuleSpec, ReadingSpecfiles) { 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()); + EXPECT_EQ("", dd.getModuleDescription()); + + dd = moduleSpecFromFile(specfile("spec25.spec")); + EXPECT_EQ("Spec25", dd.getModuleName()); + EXPECT_EQ("Just an empty module", dd.getModuleDescription()); + EXPECT_THROW(moduleSpecFromFile(specfile("spec26.spec")), ModuleSpecError); std::ifstream file; file.open(specfile("spec1.spec").c_str()); diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py index 5bce857378edd55bfae20d22b2a7aceeb73e631d..9128286ed6112c7ac4549795ec29990c2a0093d3 100644 --- a/src/lib/python/isc/config/ccsession.py +++ b/src/lib/python/isc/config/ccsession.py @@ -81,8 +81,7 @@ def create_answer(rcode, arg = None): # 'fixed' commands """Fixed names for command and configuration messages""" COMMAND_CONFIG_UPDATE = "config_update" -COMMAND_COMMANDS_UPDATE = "commands_update" -COMMAND_SPECIFICATION_UPDATE = "specification_update" +COMMAND_MODULE_SPECIFICATION_UPDATE = "module_specification_update" COMMAND_GET_COMMANDS_SPEC = "get_commands_spec" COMMAND_GET_CONFIG = "get_config" @@ -314,16 +313,9 @@ class UIModuleCCSession(MultiConfigData): # 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') + specs = self._conn.send_GET('/module_spec') 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)) + self.set_specification(isc.config.ModuleSpec(specs[module])) def request_current_config(self): """Requests the current configuration from the configuration diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py index e7dce0fd4e3872b72afebc069b8d549f6b9900df..8b74bece5a0438426301c0080361ed08ae363536 100644 --- a/src/lib/python/isc/config/cfgmgr.py +++ b/src/lib/python/isc/config/cfgmgr.py @@ -148,11 +148,23 @@ class ConfigManager: if module_name in self.module_specs: del self.module_specs[module_name] - def get_module_spec(self, module_name): + def get_module_spec(self, module_name = None): """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] + module_name. If no module name is given, a dict will + be returned with 'name': module_spec values. If the + module name is given, but does not exist, an empty dict + is returned""" + if module_name: + if module_name in self.module_specs: + return self.module_specs[module_name] + else: + # TODO: log error? + return {} + else: + result = {} + for module in self.module_specs: + result[module] = self.module_specs[module].get_full_spec() + return result def get_config_spec(self, name = None): """Returns a dict containing 'module_name': config_spec for @@ -201,13 +213,13 @@ class ConfigManager: 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)) + answer = isc.config.ccsession.create_answer(0, self.get_module_spec(module_name)) else: 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_module_spec command, argument not a dict") else: - answer = isc.config.ccsession.create_answer(0, self.get_config_spec()) + answer = isc.config.ccsession.create_answer(0, self.get_module_spec()) return answer def _handle_get_config(self, cmd): @@ -303,14 +315,10 @@ class ConfigManager: # We should make one general 'spec update for module' that # passes both specification and commands at once - spec_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_SPECIFICATION_UPDATE, - [ spec.get_module_name(), spec.get_config_spec() ]) + spec_update = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, + [ spec.get_module_name(), spec.get_full_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 + return isc.config.ccsession.create_answer(0) def handle_msg(self, msg): """Handle a command from the cc channel to the configuration manager""" diff --git a/src/lib/python/isc/config/module_spec.py b/src/lib/python/isc/config/module_spec.py index a512cb5e9b39fc62cf5ad0da011421cd1cf32059..d12a501542b5152dbed4c587ac4864fc9ff99e66 100644 --- a/src/lib/python/isc/config/module_spec.py +++ b/src/lib/python/isc/config/module_spec.py @@ -86,9 +86,19 @@ class ModuleSpec: def get_module_name(self): """Returns a string containing the name of the module as - specified by the specification given at __init__""" + specified by the specification given at __init__()""" return self._module_spec['module_name'] + def get_module_description(self): + """Returns a string containing the description of the module as + specified by the specification given at __init__(). + Returns an empty string if there is no description. + """ + if 'module_description' in self._module_spec: + return self._module_spec['module_description'] + else: + return "" + def get_full_spec(self): """Returns a dict representation of the full module specification""" return self._module_spec @@ -123,6 +133,9 @@ def _check(module_spec): raise ModuleSpecError("data specification not a dict") if "module_name" not in module_spec: raise ModuleSpecError("no module_name in module_spec") + if "module_description" in module_spec and \ + type(module_spec["module_description"]) != str: + raise ModuleSpecError("module_description is not a string") if "config_data" in module_spec: _check_config_spec(module_spec["config_data"]) if "commands" in module_spec: diff --git a/src/lib/python/isc/config/tests/module_spec_test.py b/src/lib/python/isc/config/tests/module_spec_test.py index f394afa23f4ee30b5dc5926abc5371e63f8c3404..fd546404a95c0e666cad5307465c6b7c33158c5b 100644 --- a/src/lib/python/isc/config/tests/module_spec_test.py +++ b/src/lib/python/isc/config/tests/module_spec_test.py @@ -75,6 +75,7 @@ class TestModuleSpec(unittest.TestCase): 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") + self.assertRaises(ModuleSpecError, self.read_spec_file, "spec26.spec") def validate_data(self, specfile_name, datafile_name): dd = self.read_spec_file(specfile_name); @@ -98,6 +99,10 @@ class TestModuleSpec(unittest.TestCase): module_spec = isc.config.module_spec_from_file(self.spec_file("spec1.spec"), False) self.spec1(module_spec) + module_spec = isc.config.module_spec_from_file(self.spec_file("spec25.spec"), True) + self.assertEqual("Spec25", module_spec.get_module_name()) + self.assertEqual("Just an empty module", module_spec.get_module_description()) + def test_str(self): module_spec = isc.config.module_spec_from_file(self.spec_file("spec1.spec"), False) self.assertEqual(module_spec.__str__(), "{'module_name': 'Spec1'}")