Browse Source

added error feedback to config_validate() and option to validate partial configurations in cpp version
fixed a bug in Mapelement->str() (it can now print empty map elements)
added tests for python config_data module and fixed a few bugs there


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

Jelte Jansen 15 years ago
parent
commit
3a69c8a7a8

+ 5 - 1
src/bin/auth/auth_srv.cc

@@ -122,7 +122,11 @@ AuthSrv::updateConfig(isc::data::ElementPtr config) {
         // todo: what to do with port change. restart automatically?
         // todo: what to do with port change. restart automatically?
         // ignore atm
         // ignore atm
     //}
     //}
-    std::cout << "[XX] auth: new config " << config << std::endl;
+    if (config) {
+        std::cout << "[XX] auth: new config " << config << std::endl;
+    } else {
+        std::cout << "[XX] auth: new config empty" << std::endl;
+    }
     
     
     return isc::config::createAnswer(0);
     return isc::config::createAnswer(0);
 }
 }

+ 5 - 1
src/lib/cc/cpp/data.cc

@@ -487,7 +487,11 @@ MapElement::str()
             ss << ", ";
             ss << ", ";
         }
         }
         ss << "\"" << (*it).first << "\": ";
         ss << "\"" << (*it).first << "\": ";
-        ss << (*it).second->str();
+        if ((*it).second) {
+            ss << (*it).second->str();
+        } else {
+            ss << "None";
+        }
     }
     }
     ss << "}";
     ss << "}";
     return ss.str();
     return ss.str();

+ 1 - 1
src/lib/cc/cpp/data.h

@@ -409,7 +409,7 @@ public:
     using Element::setValue;
     using Element::setValue;
     bool setValue(std::map<std::string, ElementPtr>& v) { m = v; return true; };
     bool setValue(std::map<std::string, ElementPtr>& v) { m = v; return true; };
     using Element::get;
     using Element::get;
-    ElementPtr get(const std::string& s) { return m[s]; };
+    ElementPtr get(const std::string& s) { if (contains(s)) { return m[s]; } else { return ElementPtr();} };
     using Element::set;
     using Element::set;
     void set(const std::string& s, ElementPtr p) { m[s] = p; };
     void set(const std::string& s, ElementPtr p) { m[s] = p; };
     using Element::remove;
     using Element::remove;

+ 11 - 2
src/lib/config/cpp/ccsession.cc

@@ -172,12 +172,20 @@ ElementPtr
 ModuleCCSession::handleConfigUpdate(ElementPtr new_config)
 ModuleCCSession::handleConfigUpdate(ElementPtr new_config)
 {
 {
     ElementPtr answer;
     ElementPtr answer;
+    ElementPtr errors = Element::createFromString("[]");
+    std::cout << "handleConfigUpdate " << new_config << std::endl;
     if (!config_handler_) {
     if (!config_handler_) {
         answer = createAnswer(1, module_name_ + " does not have a config handler");
         answer = createAnswer(1, module_name_ + " does not have a config handler");
-    } else if (!module_specification_.validate_config(new_config)) {
-        answer = createAnswer(2, "Error in config validation");
+    } else if (!module_specification_.validate_config(new_config, false, errors)) {
+        std::stringstream ss;
+        ss << "Error in config validation: ";
+        BOOST_FOREACH(ElementPtr error, errors->listValue()) {
+            ss << error->stringValue();
+        }
+        answer = createAnswer(2, ss.str());
     } else {
     } else {
         // handle config update
         // handle config update
+        std::cout << "handleConfigUpdate " << new_config << std::endl;
         answer = config_handler_(new_config);
         answer = config_handler_(new_config);
         int rcode;
         int rcode;
         parseAnswer(rcode, answer);
         parseAnswer(rcode, answer);
@@ -185,6 +193,7 @@ ModuleCCSession::handleConfigUpdate(ElementPtr new_config)
             config_ = new_config;
             config_ = new_config;
         }
         }
     }
     }
+    std::cout << "end handleConfigUpdate " << new_config << std::endl;
     return answer;
     return answer;
 }
 }
 
 

+ 27 - 11
src/lib/config/cpp/module_spec.cc

@@ -209,10 +209,17 @@ ModuleSpec::getModuleName()
 }
 }
 
 
 bool
 bool
-ModuleSpec::validate_config(const ElementPtr data)
+ModuleSpec::validate_config(const ElementPtr data, const bool full)
 {
 {
     ElementPtr spec = module_specification->find("module_spec/config_data");
     ElementPtr spec = module_specification->find("module_spec/config_data");
-    return validate_spec_list(spec, data);
+    return validate_spec_list(spec, data, full, ElementPtr());
+}
+
+bool
+ModuleSpec::validate_config(const ElementPtr data, const bool full, ElementPtr errors)
+{
+    ElementPtr spec = module_specification->find("module_spec/config_data");
+    return validate_spec_list(spec, data, full, errors);
 }
 }
 
 
 ModuleSpec
 ModuleSpec
@@ -279,28 +286,34 @@ check_type(ElementPtr spec, ElementPtr element)
 }
 }
 
 
 bool
 bool
-ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data) {
+ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
     if (!check_type(spec, data)) {
     if (!check_type(spec, data)) {
         // we should do some proper error feedback here
         // we should do some proper error feedback here
         // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
         // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
         // std::cout << spec << std::endl;
         // std::cout << spec << std::endl;
+        if (errors) {
+            errors->add(Element::create("Type mismatch"));
+        }
         return false;
         return false;
     }
     }
     if (data->getType() == Element::list) {
     if (data->getType() == Element::list) {
         ElementPtr list_spec = spec->get("list_item_spec");
         ElementPtr list_spec = spec->get("list_item_spec");
         BOOST_FOREACH(ElementPtr list_el, data->listValue()) {
         BOOST_FOREACH(ElementPtr list_el, data->listValue()) {
             if (!check_type(list_spec, list_el)) {
             if (!check_type(list_spec, list_el)) {
+                if (errors) {
+                    errors->add(Element::create("Type mismatch"));
+                }
                 return false;
                 return false;
             }
             }
             if (list_spec->get("item_type")->stringValue() == "map") {
             if (list_spec->get("item_type")->stringValue() == "map") {
-                if (!validate_item(list_spec, list_el)) {
+                if (!validate_item(list_spec, list_el, full, errors)) {
                     return false;
                     return false;
                 }
                 }
             }
             }
         }
         }
     }
     }
     if (data->getType() == Element::map) {
     if (data->getType() == Element::map) {
-        if (!validate_spec_list(spec->get("map_item_spec"), data)) {
+        if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
             return false;
             return false;
         }
         }
     }
     }
@@ -309,18 +322,21 @@ ModuleSpec::validate_item(const ElementPtr spec, const ElementPtr data) {
 
 
 // spec is a map with item_name etc, data is a map
 // spec is a map with item_name etc, data is a map
 bool
 bool
-ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data) {
+ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
     std::string item_name = spec->get("item_name")->stringValue();
     std::string item_name = spec->get("item_name")->stringValue();
     bool optional = spec->get("item_optional")->boolValue();
     bool optional = spec->get("item_optional")->boolValue();
     ElementPtr data_el;
     ElementPtr data_el;
-    
     data_el = data->get(item_name);
     data_el = data->get(item_name);
+    
     if (data_el) {
     if (data_el) {
-        if (!validate_item(spec, data_el)) {
+        if (!validate_item(spec, data_el, full, errors)) {
             return false;
             return false;
         }
         }
     } else {
     } else {
-        if (!optional) {
+        if (!optional && full) {
+            if (errors) {
+                errors->add(Element::create("Non-optional value missing"));
+            }
             return false;
             return false;
         }
         }
     }
     }
@@ -329,11 +345,11 @@ ModuleSpec::validate_spec(const ElementPtr spec, const ElementPtr data) {
 
 
 // spec is a list of maps, data is a map
 // spec is a list of maps, data is a map
 bool
 bool
-ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data) {
+ModuleSpec::validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors) {
     ElementPtr cur_data_el;
     ElementPtr cur_data_el;
     std::string cur_item_name;
     std::string cur_item_name;
     BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) {
     BOOST_FOREACH(ElementPtr cur_spec_el, spec->listValue()) {
-        if (!validate_spec(cur_spec_el, data)) {
+        if (!validate_spec(cur_spec_el, data, full, errors)) {
             return false;
             return false;
         }
         }
     }
     }

+ 7 - 4
src/lib/config/cpp/module_spec.h

@@ -83,12 +83,15 @@ namespace isc { namespace config {
         /// \param data The base \c Element of the data to check
         /// \param data The base \c Element of the data to check
         /// \return true if the data conforms to the specification,
         /// \return true if the data conforms to the specification,
         /// false otherwise.
         /// false otherwise.
-        bool validate_config(const ElementPtr data);
+        bool validate_config(const ElementPtr data, const bool full = false);
+
+        /// errors must be of type ListElement
+        bool validate_config(const ElementPtr data, const bool full, ElementPtr errors);
 
 
     private:
     private:
-        bool validate_item(const ElementPtr spec, const ElementPtr data);
-        bool validate_spec(const ElementPtr spec, const ElementPtr data);
-        bool validate_spec_list(const ElementPtr spec, const ElementPtr data);
+        bool validate_item(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
+        bool validate_spec(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
+        bool validate_spec_list(const ElementPtr spec, const ElementPtr data, const bool full, ElementPtr errors);
 
 
         ElementPtr module_specification;
         ElementPtr module_specification;
     };
     };

+ 5 - 2
src/lib/config/python/isc/config/cfgmgr.py

@@ -230,6 +230,8 @@ class ConfigManager:
             # todo: use api (and check the data against the definition?)
             # todo: use api (and check the data against the definition?)
             module_name = cmd[1]
             module_name = cmd[1]
             conf_part = data.find_no_exc(self.config.data, module_name)
             conf_part = data.find_no_exc(self.config.data, module_name)
+            print("[XX] cfgmgr conf part:")
+            print(conf_part)
             if conf_part:
             if conf_part:
                 data.merge(conf_part, cmd[2])
                 data.merge(conf_part, cmd[2])
                 self.cc.group_sendmsg({ "config_update": conf_part }, module_name)
                 self.cc.group_sendmsg({ "config_update": conf_part }, module_name)
@@ -246,6 +248,7 @@ class ConfigManager:
                 self.write_config()
                 self.write_config()
         elif len(cmd) == 2:
         elif len(cmd) == 2:
             # todo: use api (and check the data against the definition?)
             # todo: use api (and check the data against the definition?)
+            old_data = self.config.data.copy()
             data.merge(self.config.data, cmd[1])
             data.merge(self.config.data, cmd[1])
             # send out changed info
             # send out changed info
             got_error = False
             got_error = False
@@ -262,8 +265,8 @@ class ConfigManager:
                 self.write_config()
                 self.write_config()
                 answer = isc.config.ccsession.create_answer(0)
                 answer = isc.config.ccsession.create_answer(0)
             else:
             else:
-                # TODO rollback changes that did get through?
-                # feed back *all* errors?
+                # TODO rollback changes that did get through, should we re-send update?
+                self.config.data = old_data
                 answer = isc.config.ccsession.create_answer(1, " ".join(err_list))
                 answer = isc.config.ccsession.create_answer(1, " ".join(err_list))
         else:
         else:
             answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments")
             answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments")

+ 60 - 32
src/lib/config/python/isc/config/config_data.py

@@ -26,9 +26,10 @@ import isc.config.module_spec
 class ConfigDataError(Exception): pass
 class ConfigDataError(Exception): pass
 
 
 def check_type(spec_part, value):
 def check_type(spec_part, value):
-    """Returns true if the value is of the correct type given the
-       specification part relevant for the value. spec_part can be
-       retrieved with find_spec()"""
+    """Does nothing if the value is of the correct type given the
+       specification part relevant for the value. Raises an
+       isc.cc.data.DataTypeError exception if not. spec_part can be
+       retrieved with find_spec_part()"""
     if type(spec_part) == list:
     if type(spec_part) == list:
         data_type = "list"
         data_type = "list"
     else:
     else:
@@ -36,9 +37,9 @@ def check_type(spec_part, value):
 
 
     if data_type == "integer" and type(value) != int:
     if data_type == "integer" and type(value) != int:
         raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
         raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
-    elif data_type == "real" and type(value) != double:
+    elif data_type == "real" and type(value) != float:
         raise isc.cc.data.DataTypeError(str(value) + " is not a real")
         raise isc.cc.data.DataTypeError(str(value) + " is not a real")
-    elif data_type == "boolean" and type(value) != boolean:
+    elif data_type == "boolean" and type(value) != bool:
         raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
         raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
     elif data_type == "string" and type(value) != str:
     elif data_type == "string" and type(value) != str:
         raise isc.cc.data.DataTypeError(str(value) + " is not a string")
         raise isc.cc.data.DataTypeError(str(value) + " is not a string")
@@ -52,7 +53,7 @@ def check_type(spec_part, value):
         # todo: check types of map contents too
         # todo: check types of map contents too
         raise isc.cc.data.DataTypeError(str(value) + " is not a map")
         raise isc.cc.data.DataTypeError(str(value) + " is not a map")
 
 
-def find_spec(element, identifier):
+def find_spec_part(element, identifier):
     """find the data definition for the given identifier
     """find the data definition for the given identifier
        returns either a map with 'item_name' etc, or a list of those"""
        returns either a map with 'item_name' etc, or a list of those"""
     if identifier == "":
     if identifier == "":
@@ -65,6 +66,14 @@ def find_spec(element, identifier):
             cur_el = cur_el[id]
             cur_el = cur_el[id]
         elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
         elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
             pass
             pass
+        elif type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
+            found = False
+            for cur_el_item in cur_el['map_item_spec']:
+                if cur_el_item['item_name'] == id:
+                    cur_el = cur_el_item
+                    found = True
+            if not found:
+                raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
         elif type(cur_el) == list:
         elif type(cur_el) == list:
             found = False
             found = False
             for cur_el_item in cur_el:
             for cur_el_item in cur_el:
@@ -84,24 +93,30 @@ def spec_name_list(spec, prefix="", recurse=False):
     if prefix != "" and not prefix.endswith("/"):
     if prefix != "" and not prefix.endswith("/"):
         prefix += "/"
         prefix += "/"
     if type(spec) == dict:
     if type(spec) == dict:
-        for name in spec:
-            result.append(prefix + name + "/")
-            if recurse:
-                result.extend(spec_name_list(spec[name],name, recurse))
+        if 'map_item_spec' in spec:
+            for map_el in spec['map_item_spec']:
+                name = map_el['item_name']
+                if map_el['item_type'] == 'map':
+                    name += "/"
+                result.append(prefix + name)
+        else:
+            for name in spec:
+                result.append(prefix + name + "/")
+                if recurse:
+                    print("[XX] recurse1")
+                    result.extend(spec_name_list(spec[name],name, recurse))
     elif type(spec) == list:
     elif type(spec) == list:
         for list_el in spec:
         for list_el in spec:
             if 'item_name' in list_el:
             if 'item_name' in list_el:
-                if list_el['item_type'] == dict:
-                    if recurse:
-                        result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
+                if list_el['item_type'] == "map" and recurse:
+                    result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
                 else:
                 else:
                     name = list_el['item_name']
                     name = list_el['item_name']
                     if list_el['item_type'] in ["list", "map"]:
                     if list_el['item_type'] in ["list", "map"]:
                         name += "/"
                         name += "/"
-                    result.append(name)
+                    result.append(prefix + name)
     return result
     return result
 
 
-
 class ConfigData:
 class ConfigData:
     """This class stores the module specs and the current non-default
     """This class stores the module specs and the current non-default
        config values. It provides functions to get the actual value or
        config values. It provides functions to get the actual value or
@@ -118,11 +133,12 @@ class ConfigData:
     def get_value(self, identifier):
     def get_value(self, identifier):
         """Returns a tuple where the first item is the value at the
         """Returns a tuple where the first item is the value at the
            given identifier, and the second item is a bool which is
            given identifier, and the second item is a bool which is
-           true if the value is an unset default"""
+           true if the value is an unset default. Raises an
+           isc.cc.data.DataNotFoundError if the identifier is bad"""
         value = isc.cc.data.find_no_exc(self.data, identifier)
         value = isc.cc.data.find_no_exc(self.data, identifier)
-        if value:
+        if value != None:
             return value, False
             return value, False
-        spec = find_spec(self.specification.get_config_spec(), identifier)
+        spec = find_spec_part(self.specification.get_config_spec(), identifier)
         if spec and 'item_default' in spec:
         if spec and 'item_default' in spec:
             return spec['item_default'], True
             return spec['item_default'], True
         return None, False
         return None, False
@@ -144,7 +160,7 @@ class ConfigData:
            all 'sub'options at the given identifier. If recurse is True,
            all 'sub'options at the given identifier. If recurse is True,
            it will also add all identifiers of all children, if any"""
            it will also add all identifiers of all children, if any"""
         if identifier:
         if identifier:
-            spec = find_spec(self.specification.get_config_spec(), identifier, recurse)
+            spec = find_spec_part(self.specification.get_config_spec(), identifier)
             return spec_name_list(spec, identifier + "/")
             return spec_name_list(spec, identifier + "/")
         return spec_name_list(self.specification.get_config_spec(), "", recurse)
         return spec_name_list(self.specification.get_config_spec(), "", recurse)
 
 
@@ -183,7 +199,8 @@ class MultiConfigData:
         self._specifications[spec.get_module_name()] = spec
         self._specifications[spec.get_module_name()] = spec
 
 
     def get_module_spec(self, module):
     def get_module_spec(self, module):
-        """Returns the ModuleSpec for the module with the given name"""
+        """Returns the ModuleSpec for the module with the given name.
+           If there is no such module, it returns None"""
         if module in self._specifications:
         if module in self._specifications:
             return self._specifications[module]
             return self._specifications[module]
         else:
         else:
@@ -193,14 +210,16 @@ class MultiConfigData:
         """Returns the specification for the item at the given
         """Returns the specification for the item at the given
            identifier, or None if not found. The first part of the
            identifier, or None if not found. The first part of the
            identifier (up to the first /) is interpreted as the module
            identifier (up to the first /) is interpreted as the module
-           name."""
+           name. Returns None if not found."""
         if identifier[0] == '/':
         if identifier[0] == '/':
             identifier = identifier[1:]
             identifier = identifier[1:]
         module, sep, id = identifier.partition("/")
         module, sep, id = identifier.partition("/")
         try:
         try:
-            return find_spec(self._specifications[module].get_config_spec(), id)
+            return find_spec_part(self._specifications[module].get_config_spec(), id)
         except isc.cc.data.DataNotFoundError as dnfe:
         except isc.cc.data.DataNotFoundError as dnfe:
             return None
             return None
+        except KeyError as ke:
+            return None
 
 
     # this function should only be called by __request_config
     # this function should only be called by __request_config
     def _set_current_config(self, config):
     def _set_current_config(self, config):
@@ -252,7 +271,7 @@ class MultiConfigData:
             identifier = identifier[1:]
             identifier = identifier[1:]
         module, sep, id = identifier.partition("/")
         module, sep, id = identifier.partition("/")
         try:
         try:
-            spec = find_spec(self._specifications[module].get_config_spec(), id)
+            spec = find_spec_part(self._specifications[module].get_config_spec(), id)
             if 'item_default' in spec:
             if 'item_default' in spec:
                 return spec['item_default']
                 return spec['item_default']
             else:
             else:
@@ -268,13 +287,13 @@ class MultiConfigData:
            (local change, current setting, default as specified by the
            (local change, current setting, default as specified by the
            specification, or not found at all)."""
            specification, or not found at all)."""
         value = self.get_local_value(identifier)
         value = self.get_local_value(identifier)
-        if value:
+        if value != None:
             return value, self.LOCAL
             return value, self.LOCAL
         value = self.get_current_value(identifier)
         value = self.get_current_value(identifier)
-        if value:
+        if value != None:
             return value, self.CURRENT
             return value, self.CURRENT
         value = self.get_default_value(identifier)
         value = self.get_default_value(identifier)
-        if value:
+        if value != None:
             return value, self.DEFAULT
             return value, self.DEFAULT
         return None, self.NONE
         return None, self.NONE
 
 
@@ -305,7 +324,7 @@ class MultiConfigData:
             module, sep, id = identifier.partition('/')
             module, sep, id = identifier.partition('/')
             spec = self.get_module_spec(module)
             spec = self.get_module_spec(module)
             if spec:
             if spec:
-                spec_part = find_spec(spec.get_config_spec(), id)
+                spec_part = find_spec_part(spec.get_config_spec(), id)
                 print(spec_part)
                 print(spec_part)
                 if type(spec_part) == list:
                 if type(spec_part) == list:
                     for item in spec_part:
                     for item in spec_part:
@@ -357,12 +376,15 @@ class MultiConfigData:
         return result
         return result
 
 
     def set_value(self, identifier, value):
     def set_value(self, identifier, value):
-        """Set the local value at the given identifier to value"""
+        """Set the local value at the given identifier to value. If
+           there is a specification for the given identifier, the type
+           is checked."""
         spec_part = self.find_spec_part(identifier)
         spec_part = self.find_spec_part(identifier)
-        check_type(spec_part, value)
+        if spec_part != None:
+            check_type(spec_part, value)
         isc.cc.data.set(self._local_changes, identifier, value)
         isc.cc.data.set(self._local_changes, identifier, value)
  
  
-    def get_config_item_list(self, identifier = None):
+    def get_config_item_list(self, identifier = None, recurse = False):
         """Returns a list of strings containing the item_names of
         """Returns a list of strings containing the item_names of
            the child items at the given identifier. If no identifier is
            the child items at the given identifier. If no identifier is
            specified, returns a list of module names. The first part of
            specified, returns a list of module names. The first part of
@@ -370,6 +392,12 @@ class MultiConfigData:
            module name"""
            module name"""
         if identifier:
         if identifier:
             spec = self.find_spec_part(identifier)
             spec = self.find_spec_part(identifier)
-            return spec_name_list(spec, identifier + "/")
+            return spec_name_list(spec, identifier + "/", recurse)
         else:
         else:
-            return self._specifications.keys()
+            if recurse:
+                id_list = []
+                for module in self._specifications:
+                    id_list.extend(spec_name_list(self._specifications[module], module, recurse))
+                return id_list
+            else:
+                return list(self._specifications.keys())

+ 254 - 6
src/lib/config/python/isc/config/config_data_test.py

@@ -28,13 +28,261 @@ class TestConfigData(unittest.TestCase):
             self.data_path = os.environ['CONFIG_TESTDATA_PATH']
             self.data_path = os.environ['CONFIG_TESTDATA_PATH']
         else:
         else:
             self.data_path = "../../../testdata"
             self.data_path = "../../../testdata"
+        spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.cd = ConfigData(spec)
 
 
-    def test_module_spec_from_file(self):
-        spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
-        cd = ConfigData(spec)
-        self.assertEqual(cd.specification, spec)
-        self.assertEqual(cd.data, {})
-        self.assertRaises(ConfigDataError, ConfigData, 1)
+    #def test_module_spec_from_file(self):
+    #    spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
+    #    cd = ConfigData(spec)
+    #    self.assertEqual(cd.specification, spec)
+    #    self.assertEqual(cd.data, {})
+    #    self.assertRaises(ConfigDataError, ConfigData, 1)
+
+    def test_check_type(self):
+        config_spec = self.cd.get_module_spec().get_config_spec()
+        spec_part = find_spec_part(config_spec, "item1")
+        check_type(spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
+        
+        spec_part = find_spec_part(config_spec, "item2")
+        check_type(spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
+        
+        spec_part = find_spec_part(config_spec, "item3")
+        check_type(spec_part, True)
+        check_type(spec_part, False)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
+        
+        spec_part = find_spec_part(config_spec, "item4")
+        check_type(spec_part, "asdf")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
+        
+        spec_part = find_spec_part(config_spec, "item5")
+        check_type(spec_part, ["a", "b"])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "a": 1 })
+        
+        spec_part = find_spec_part(config_spec, "item6")
+        check_type(spec_part, { "value1": "aaa", "value2": 2 })
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, [ 1, 2 ])
+        #self.assertRaises(isc.cc.data.DataTypeError, check_type, spec_part, { "value1": 1 })
+
+    def test_find_spec_part(self):
+        config_spec = self.cd.get_module_spec().get_config_spec()
+        spec_part = find_spec_part(config_spec, "item1")
+        self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
+        self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item")
+        self.assertRaises(isc.cc.data.DataNotFoundError, find_spec_part, config_spec, "no_such_item/multilevel")
+        spec_part = find_spec_part(config_spec, "item6/value1")
+        #print(spec_part)
+        self.assertEqual({'item_name': 'value1', 'item_type': 'string', 'item_optional': True, 'item_default': 'default'}, spec_part)
+
+    def test_spec_name_list(self):
+        name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+        name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+
+    def test_get_value(self):
+        value, default = self.cd.get_value("item1")
+        self.assertEqual(1, value)
+        self.assertEqual(True, default)
+        value, default = self.cd.get_value("item2")
+        self.assertEqual(1.1, value)
+        self.assertEqual(True, default)
+        value, default = self.cd.get_value("item3")
+        self.assertEqual(True, value)
+        self.assertEqual(True, default)
+        value, default = self.cd.get_value("item4")
+        self.assertEqual("test", value)
+        self.assertEqual(True, default)
+        value, default = self.cd.get_value("item5")
+        self.assertEqual(["a", "b"], value)
+        self.assertEqual(True, default)
+        value, default = self.cd.get_value("item6")
+        self.assertEqual({}, value)
+        self.assertEqual(True, default)
+        self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "no_such_item")
+        value, default = self.cd.get_value("item6/value2")
+        self.assertEqual(None, value)
+        self.assertEqual(False, default)
+
+    def test_set_local_config(self):
+        self.cd.set_local_config({"item1": 2})
+        value, default = self.cd.get_value("item1")
+        self.assertEqual(2, value)
+        self.assertEqual(False, default)
+
+    def test_get_local_config(self):
+        local_config = self.cd.get_local_config()
+        self.assertEqual({}, local_config)
+        my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
+        self.cd.set_local_config(my_config)
+        self.assertEqual(my_config, self.cd.get_local_config())
+
+    def test_get_item_list(self):
+        name_list = self.cd.get_item_list()
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+        name_list = self.cd.get_item_list("", True)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+        name_list = self.cd.get_item_list("item6", False)
+        self.assertEqual(['item6/value1', 'item6/value2'], name_list)
+
+    def test_get_full_config(self):
+        full_config = self.cd.get_full_config()
+        self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5/": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
+        my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
+        self.cd.set_local_config(my_config)
+        full_config = self.cd.get_full_config()
+        self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5/": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
+
+class TestMultiConfigData(unittest.TestCase):
+    def setUp(self):
+        if 'CONFIG_TESTDATA_PATH' in os.environ:
+            self.data_path = os.environ['CONFIG_TESTDATA_PATH']
+        else:
+            self.data_path = "../../../testdata"
+        self.mcd = MultiConfigData()
+        
+    def test_init(self):
+        self.assertEqual({}, self.mcd._specifications)
+        self.assertEqual({}, self.mcd._current_config)
+        self.assertEqual({}, self.mcd._local_changes)
+
+    def test_set_specification(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
+        self.mcd.set_specification(module_spec)
+        self.assert_(module_spec.get_module_name() in self.mcd._specifications)
+        self.assertEquals(module_spec, self.mcd._specifications[module_spec.get_module_name()])
+
+    def test_get_module_spec(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
+        self.mcd.set_specification(module_spec)
+        module_spec2 = self.mcd.get_module_spec(module_spec.get_module_name())
+        self.assertEqual(module_spec, module_spec2)
+        module_spec3 = self.mcd.get_module_spec("no_such_module")
+        self.assertEqual(None, module_spec3)
+
+    def test_find_spec_part(self):
+        spec_part = self.mcd.find_spec_part("Spec2/item1")
+        self.assertEqual(None, spec_part)
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        spec_part = self.mcd.find_spec_part("Spec2/item1")
+        self.assertEqual({'item_name': 'item1', 'item_type': 'integer', 'item_optional': False, 'item_default': 1, }, spec_part)
+
+    def test_get_local_changes(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        local_changes = self.mcd.get_local_changes()
+        self.assertEqual({}, local_changes)
+        self.mcd.set_value("Spec2/item1", 2)
+        local_changes = self.mcd.get_local_changes()
+        self.assertEqual({"Spec2": { "item1": 2}}, local_changes)
+        
+
+    def test_clear_local_changes(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        self.mcd.set_value("Spec2/item1", 2)
+        self.mcd.clear_local_changes()
+        local_changes = self.mcd.get_local_changes()
+        self.assertEqual({}, local_changes)
+        pass
+
+    def test_get_local_value(self):
+        value = self.mcd.get_local_value("Spec2/item1")
+        self.assertEqual(None, value)
+        self.mcd.set_value("Spec2/item1", 2)
+        value = self.mcd.get_local_value("Spec2/item1")
+        self.assertEqual(2, value)
+
+    def test_get_current_value(self):
+        value = self.mcd.get_current_value("Spec2/item1")
+        self.assertEqual(None, value)
+        self.mcd._current_config = { "Spec2": { "item1": 3 } }
+        value = self.mcd.get_current_value("Spec2/item1")
+        self.assertEqual(3, value)
+        pass
+
+    def test_get_default_value(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        value = self.mcd.get_default_value("Spec2/item1")
+        self.assertEqual(1, value)
+        value = self.mcd.get_default_value("Spec2/item6/value1")
+        self.assertEqual('default', value)
+        value = self.mcd.get_default_value("Spec2/item6/value2")
+        self.assertEqual(None, value)
+        value = self.mcd.get_default_value("Spec2/no_such_item/asdf")
+        self.assertEqual(None, value)
+
+    def test_get_value(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        self.mcd.set_value("Spec2/item1", 2)
+        value,status = self.mcd.get_value("Spec2/item1")
+        self.assertEqual(2, value)
+        self.assertEqual(MultiConfigData.LOCAL, status)
+        value,status = self.mcd.get_value("Spec2/item2")
+        self.assertEqual(1.1, value)
+        self.assertEqual(MultiConfigData.DEFAULT, status)
+        self.mcd._current_config = { "Spec2": { "item3": False } }
+        value,status = self.mcd.get_value("Spec2/item3")
+        self.assertEqual(False, value)
+        self.assertEqual(MultiConfigData.CURRENT, status)
+        value,status = self.mcd.get_value("Spec2/no_such_item")
+        self.assertEqual(None, value)
+        self.assertEqual(MultiConfigData.NONE, status)
+
+    def test_get_value_maps(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        maps = self.mcd.get_value_maps()
+        self.assertEqual([{'default': False, 'type': 'module', 'name': 'Spec2', 'value': None, 'modified': False}], maps)
+
+    def test_set_value(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        self.mcd.set_value("Spec2/item1", 2)
+        self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf")
+        self.mcd.set_value("Spec2/no_such_item", 4)
+
+    def test_get_config_item_list(self):
+        config_items = self.mcd.get_config_item_list()
+        self.assertEqual([], config_items)
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.mcd.set_specification(module_spec)
+        config_items = self.mcd.get_config_item_list()
+        self.assertEqual(['Spec2'], config_items)
+        config_items = self.mcd.get_config_item_list("Spec2")
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items)
+        config_items = self.mcd.get_config_item_list("Spec2", True)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()