Browse Source

Merged in branches/trac90
(module descriptions in spec files)
See also https://bind10.isc.org/ticket/90


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@1856 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen 15 years ago
parent
commit
1db7be2208

+ 1 - 0
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",

+ 1 - 0
src/bin/bind10/bob.spec

@@ -1,6 +1,7 @@
 {
   "module_spec": {
     "module_name": "Boss",
+    "module_description": "Master process",
     "config_data": [
       {
         "item_name": "example_string",

+ 7 - 11
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"]:

+ 10 - 18
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 
 

+ 1 - 0
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",

+ 1 - 0
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",

+ 2 - 0
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

+ 11 - 0
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)
 {

+ 4 - 0
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.

+ 7 - 0
src/lib/config/testdata/spec25.spec

@@ -0,0 +1,7 @@
+{
+  "module_spec": {
+    "module_name": "Spec25",
+    "module_description": "Just an empty module"
+  }
+}
+

+ 6 - 0
src/lib/config/testdata/spec26.spec

@@ -0,0 +1,6 @@
+{
+  "module_spec": {
+    "module_name": "Spec26",
+    "module_description": 1
+  }
+}

+ 6 - 0
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());

+ 3 - 11
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

+ 21 - 13
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"""

+ 14 - 1
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:

+ 5 - 0
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'}")