Browse Source

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

Jelte Jansen 15 years ago
parent
commit
275dbabd2d

+ 2 - 2
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")

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

+ 80 - 20
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()

+ 2 - 2
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")

+ 45 - 267
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)

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