diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc index 75eb5bcd1575cb6251d49a750456827c4bd4ab0c..27f67c0f4b1e466f492adb92036eb6d3f43dc14b 100644 --- a/src/bin/auth/auth_srv.cc +++ b/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? // 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); } diff --git a/src/lib/cc/cpp/data.cc b/src/lib/cc/cpp/data.cc index ee4827b79958ba24598553c48aa0ae5dca45423f..b75eba11c39913795e2207e63f9f2cc0969043f2 100644 --- a/src/lib/cc/cpp/data.cc +++ b/src/lib/cc/cpp/data.cc @@ -487,7 +487,11 @@ MapElement::str() ss << ", "; } ss << "\"" << (*it).first << "\": "; - ss << (*it).second->str(); + if ((*it).second) { + ss << (*it).second->str(); + } else { + ss << "None"; + } } ss << "}"; return ss.str(); diff --git a/src/lib/cc/cpp/data.h b/src/lib/cc/cpp/data.h index ad1fa49bab01a8c6008465fa1ee25b03e21fdfaf..220b3d04feb0306a357bdb0c98778e69944e98e5 100644 --- a/src/lib/cc/cpp/data.h +++ b/src/lib/cc/cpp/data.h @@ -409,7 +409,7 @@ public: using Element::setValue; bool setValue(std::map& v) { m = v; return true; }; 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; void set(const std::string& s, ElementPtr p) { m[s] = p; }; using Element::remove; diff --git a/src/lib/config/cpp/ccsession.cc b/src/lib/config/cpp/ccsession.cc index 4883ca8a9024c67aa1e784283fe17807529fbd52..306eb8ea6b5f5b7226b9f7b8cc64c867ba9ee476 100644 --- a/src/lib/config/cpp/ccsession.cc +++ b/src/lib/config/cpp/ccsession.cc @@ -172,12 +172,20 @@ ElementPtr ModuleCCSession::handleConfigUpdate(ElementPtr new_config) { ElementPtr answer; + ElementPtr errors = Element::createFromString("[]"); + std::cout << "handleConfigUpdate " << new_config << std::endl; if (!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 { // handle config update + std::cout << "handleConfigUpdate " << new_config << std::endl; answer = config_handler_(new_config); int rcode; parseAnswer(rcode, answer); @@ -185,6 +193,7 @@ ModuleCCSession::handleConfigUpdate(ElementPtr new_config) config_ = new_config; } } + std::cout << "end handleConfigUpdate " << new_config << std::endl; return answer; } diff --git a/src/lib/config/cpp/module_spec.cc b/src/lib/config/cpp/module_spec.cc index 154008b3bd6f4d11f047fb492d3e2a7f9b02926c..27314f38d1c9f494fa8132f4d4f424ed2c2ce471 100644 --- a/src/lib/config/cpp/module_spec.cc +++ b/src/lib/config/cpp/module_spec.cc @@ -209,10 +209,17 @@ ModuleSpec::getModuleName() } 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"); - 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 @@ -279,28 +286,34 @@ check_type(ElementPtr spec, ElementPtr element) } 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)) { // we should do some proper error feedback here // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl; // std::cout << spec << std::endl; + if (errors) { + errors->add(Element::create("Type mismatch")); + } return false; } if (data->getType() == Element::list) { ElementPtr list_spec = spec->get("list_item_spec"); BOOST_FOREACH(ElementPtr list_el, data->listValue()) { if (!check_type(list_spec, list_el)) { + if (errors) { + errors->add(Element::create("Type mismatch")); + } return false; } 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; } } } } 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; } } @@ -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 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(); bool optional = spec->get("item_optional")->boolValue(); ElementPtr data_el; - data_el = data->get(item_name); + if (data_el) { - if (!validate_item(spec, data_el)) { + if (!validate_item(spec, data_el, full, errors)) { return false; } } else { - if (!optional) { + if (!optional && full) { + if (errors) { + errors->add(Element::create("Non-optional value missing")); + } 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 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; std::string cur_item_name; 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; } } diff --git a/src/lib/config/cpp/module_spec.h b/src/lib/config/cpp/module_spec.h index af96a30c69184d97c650721c30b4bc58ab62662c..97059e82fb4cb1666df7e91ea70ee4ae9472c0f6 100644 --- a/src/lib/config/cpp/module_spec.h +++ b/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 /// \return true if the data conforms to the specification, /// 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: - 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; }; diff --git a/src/lib/config/python/isc/config/cfgmgr.py b/src/lib/config/python/isc/config/cfgmgr.py index ea9f9986bca351039cebcceb811bcafc85b8eeb9..c4425cd5cc86b5f40b342aaf921c7b18caea2c58 100644 --- a/src/lib/config/python/isc/config/cfgmgr.py +++ b/src/lib/config/python/isc/config/cfgmgr.py @@ -230,6 +230,8 @@ class ConfigManager: # todo: use api (and check the data against the definition?) module_name = cmd[1] conf_part = data.find_no_exc(self.config.data, module_name) + print("[XX] cfgmgr conf part:") + print(conf_part) if conf_part: data.merge(conf_part, cmd[2]) self.cc.group_sendmsg({ "config_update": conf_part }, module_name) @@ -246,6 +248,7 @@ class ConfigManager: self.write_config() elif len(cmd) == 2: # todo: use api (and check the data against the definition?) + old_data = self.config.data.copy() data.merge(self.config.data, cmd[1]) # send out changed info got_error = False @@ -262,8 +265,8 @@ class ConfigManager: self.write_config() answer = isc.config.ccsession.create_answer(0) 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)) else: answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments") diff --git a/src/lib/config/python/isc/config/config_data.py b/src/lib/config/python/isc/config/config_data.py index ccc281e021981fc1cb80174494b1aa1a01c0db70..9270f499d10f62f71ffaba78552e7925c6a71acd 100644 --- a/src/lib/config/python/isc/config/config_data.py +++ b/src/lib/config/python/isc/config/config_data.py @@ -26,9 +26,10 @@ import isc.config.module_spec class ConfigDataError(Exception): pass 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: data_type = "list" else: @@ -36,9 +37,9 @@ def check_type(spec_part, value): if data_type == "integer" and type(value) != int: 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") - 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") elif data_type == "string" and type(value) != str: 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 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 returns either a map with 'item_name' etc, or a list of those""" if identifier == "": @@ -65,6 +66,14 @@ def find_spec(element, identifier): 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) == 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: found = False 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("/"): prefix += "/" 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: 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)) + 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: name = list_el['item_name'] if list_el['item_type'] in ["list", "map"]: name += "/" - result.append(name) + result.append(prefix + name) return result - class ConfigData: """This class stores the module specs and the current non-default config values. It provides functions to get the actual value or @@ -118,11 +133,12 @@ class ConfigData: 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""" + 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) - if value: + if value != None: 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: return spec['item_default'], True return None, False @@ -144,7 +160,7 @@ class ConfigData: all 'sub'options at the given identifier. If recurse is True, it will also add all identifiers of all children, if any""" 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(self.specification.get_config_spec(), "", recurse) @@ -183,7 +199,8 @@ class MultiConfigData: self._specifications[spec.get_module_name()] = spec 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: return self._specifications[module] else: @@ -193,14 +210,16 @@ class MultiConfigData: """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.""" + name. Returns None if not found.""" if identifier[0] == '/': identifier = identifier[1:] module, sep, id = identifier.partition("/") 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: return None + except KeyError as ke: + return None # this function should only be called by __request_config def _set_current_config(self, config): @@ -252,7 +271,7 @@ class MultiConfigData: identifier = identifier[1:] module, sep, id = identifier.partition("/") 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: return spec['item_default'] else: @@ -268,13 +287,13 @@ class MultiConfigData: (local change, current setting, default as specified by the specification, or not found at all).""" value = self.get_local_value(identifier) - if value: + if value != None: return value, self.LOCAL value = self.get_current_value(identifier) - if value: + if value != None: return value, self.CURRENT value = self.get_default_value(identifier) - if value: + if value != None: return value, self.DEFAULT return None, self.NONE @@ -305,7 +324,7 @@ class MultiConfigData: module, sep, id = identifier.partition('/') spec = self.get_module_spec(module) if spec: - spec_part = find_spec(spec.get_config_spec(), id) + spec_part = find_spec_part(spec.get_config_spec(), id) print(spec_part) if type(spec_part) == list: for item in spec_part: @@ -357,12 +376,15 @@ class MultiConfigData: return result 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) - check_type(spec_part, value) + if spec_part != None: + check_type(spec_part, 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 the child items at the given identifier. If no identifier is specified, returns a list of module names. The first part of @@ -370,6 +392,12 @@ class MultiConfigData: module name""" if identifier: spec = self.find_spec_part(identifier) - return spec_name_list(spec, identifier + "/") + return spec_name_list(spec, identifier + "/", recurse) 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()) diff --git a/src/lib/config/python/isc/config/config_data_test.py b/src/lib/config/python/isc/config/config_data_test.py index a5e383a017c8b7bf370d67f67140d353dce55cea..19a8e5a879b1d6cdd49b13b6d2d1f7d00950a50a 100644 --- a/src/lib/config/python/isc/config/config_data_test.py +++ b/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'] else: 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__': unittest.main()