[svn] commit: r814 - in /branches/jelte-configuration/src: bin/auth/ bin/bind10/ bin/bindctl/ lib/cc/python/isc/cc/ lib/config/cpp/ lib/config/python/isc/config/ lib/config/testdata/

BIND 10 source code commits bind10-changes at lists.isc.org
Sat Feb 13 22:07:59 UTC 2010


Author: jelte
Date: Sat Feb 13 22:07:58 2010
New Revision: 814

Log:
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


Added:
    branches/jelte-configuration/src/lib/config/python/isc/config/config_data.py
    branches/jelte-configuration/src/lib/config/python/isc/config/config_data_test.py
Modified:
    branches/jelte-configuration/src/bin/auth/auth.spec
    branches/jelte-configuration/src/bin/bind10/bind10.py.in
    branches/jelte-configuration/src/bin/bind10/bob.spec
    branches/jelte-configuration/src/bin/bindctl/bindcmd.py
    branches/jelte-configuration/src/bin/bindctl/bindctl.in
    branches/jelte-configuration/src/bin/bindctl/bindctl.py
    branches/jelte-configuration/src/lib/cc/python/isc/cc/data.py
    branches/jelte-configuration/src/lib/config/cpp/ccsession.cc
    branches/jelte-configuration/src/lib/config/python/isc/config/__init__.py
    branches/jelte-configuration/src/lib/config/python/isc/config/ccsession.py
    branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr.py
    branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr_test.py
    branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition.py
    branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition_test.py
    branches/jelte-configuration/src/lib/config/testdata/spec2.spec
    branches/jelte-configuration/src/lib/config/testdata/spec3.spec

Modified: branches/jelte-configuration/src/bin/auth/auth.spec
==============================================================================
--- branches/jelte-configuration/src/bin/auth/auth.spec (original)
+++ branches/jelte-configuration/src/bin/auth/auth.spec Sat Feb 13 22:07:58 2010
@@ -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": ""
+          }
+      }
+    ]
   }
 }
 

Modified: branches/jelte-configuration/src/bin/bind10/bind10.py.in
==============================================================================
--- branches/jelte-configuration/src/bin/bind10/bind10.py.in (original)
+++ branches/jelte-configuration/src/bin/bind10/bind10.py.in Sat Feb 13 22:07:58 2010
@@ -478,7 +478,7 @@
 
         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)
 

Modified: branches/jelte-configuration/src/bin/bind10/bob.spec
==============================================================================
--- branches/jelte-configuration/src/bin/bind10/bob.spec (original)
+++ branches/jelte-configuration/src/bin/bind10/bob.spec Sat Feb 13 22:07:58 2010
@@ -4,6 +4,12 @@
     "config_data": [
       {
         "item_name": "some_string",
+        "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!"

Modified: branches/jelte-configuration/src/bin/bindctl/bindcmd.py
==============================================================================
--- branches/jelte-configuration/src/bin/bindctl/bindcmd.py (original)
+++ branches/jelte-configuration/src/bin/bindctl/bindcmd.py Sat Feb 13 22:07:58 2010
@@ -85,7 +85,7 @@
                 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 @@
         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 @@
                     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 @@
                         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:

Modified: branches/jelte-configuration/src/bin/bindctl/bindctl.in
==============================================================================
--- branches/jelte-configuration/src/bin/bindctl/bindctl.in (original)
+++ branches/jelte-configuration/src/bin/bindctl/bindctl.in Sat Feb 13 22:07:58 2010
@@ -5,7 +5,7 @@
 
 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}

Modified: branches/jelte-configuration/src/bin/bindctl/bindctl.py
==============================================================================
--- branches/jelte-configuration/src/bin/bindctl/bindctl.py (original)
+++ branches/jelte-configuration/src/bin/bindctl/bindctl.py Sat Feb 13 22:07:58 2010
@@ -67,12 +67,17 @@
     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?")
 
 

Modified: branches/jelte-configuration/src/lib/cc/python/isc/cc/data.py
==============================================================================
--- branches/jelte-configuration/src/lib/cc/python/isc/cc/data.py (original)
+++ branches/jelte-configuration/src/lib/cc/python/isc/cc/data.py Sat Feb 13 22:07:58 2010
@@ -84,9 +84,10 @@
             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 @@
             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 @@
         # 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)

Modified: branches/jelte-configuration/src/lib/config/cpp/ccsession.cc
==============================================================================
    (empty)

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/__init__.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/__init__.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/__init__.py Sat Feb 13 22:07:58 2010
@@ -1,2 +1,3 @@
 from isc.config.ccsession import *
+from isc.config.config_data import *
 from isc.config.datadefinition import *

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/ccsession.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/ccsession.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/ccsession.py Sat Feb 13 22:07:58 2010
@@ -27,7 +27,7 @@
 
 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 @@
 
     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):

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr.py Sat Feb 13 22:07:58 2010
@@ -106,9 +106,12 @@
        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 @@
         """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 set_data_spec(self, spec):
+        #data_def = isc.config.DataDefinition(spec)
+        self.data_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_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 @@
             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 @@
                 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 @@
         # 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 @@
                 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):

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr_test.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr_test.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/cfgmgr_test.py Sat Feb 13 22:07:58 2010
@@ -102,12 +102,10 @@
         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 @@
         # 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 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.assertEquals(answer, expected_answer)
         self.assertEqual(expected_answer, answer)
 
     def test_handle_msg(self):
@@ -150,8 +147,8 @@
         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 @@
                          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))
         
         
 

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition.py Sat Feb 13 22:07:58 2010
@@ -27,16 +27,28 @@
 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 @@
            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 @@
         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 @@
        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 @@
        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)
 

Modified: branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition_test.py
==============================================================================
--- branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition_test.py (original)
+++ branches/jelte-configuration/src/lib/config/python/isc/config/datadefinition_test.py Sat Feb 13 22:07:58 2010
@@ -25,29 +25,29 @@
 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 @@
         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 @@
         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()

Modified: branches/jelte-configuration/src/lib/config/testdata/spec2.spec
==============================================================================
--- branches/jelte-configuration/src/lib/config/testdata/spec2.spec (original)
+++ branches/jelte-configuration/src/lib/config/testdata/spec2.spec Sat Feb 13 22:07:58 2010
@@ -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": []
+      }
     ]
   }
 }

Modified: branches/jelte-configuration/src/lib/config/testdata/spec3.spec
==============================================================================
--- branches/jelte-configuration/src/lib/config/testdata/spec3.spec (original)
+++ branches/jelte-configuration/src/lib/config/testdata/spec3.spec Sat Feb 13 22:07:58 2010
@@ -1,6 +1,6 @@
 {
   "data_specification": {
-    "module_name": "Spec2",
+    "module_name": "Spec3",
     "config_data": [
       {
         "item_type": "integer",




More information about the bind10-changes mailing list