Commit 3a69c8a7 authored by Jelte Jansen's avatar Jelte Jansen

added error feedback to config_validate() and option to validate partial...

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
parent 633e231b
......@@ -122,7 +122,11 @@ AuthSrv::updateConfig(isc::data::ElementPtr config) {
// todo: what to do with port change. restart automatically?
// ignore atm
//}
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);
}
......@@ -487,7 +487,11 @@ MapElement::str()
ss << ", ";
}
ss << "\"" << (*it).first << "\": ";
if ((*it).second) {
ss << (*it).second->str();
} else {
ss << "None";
}
}
ss << "}";
return ss.str();
......
......@@ -409,7 +409,7 @@ public:
using Element::setValue;
bool setValue(std::map<std::string, ElementPtr>& 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;
......
......@@ -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;
}
......
......@@ -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;
}
}
......
......@@ -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;
};
......
......@@ -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")
......
......@@ -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:
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:
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)
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:
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 self._specifications.keys()
return list(self._specifications.keys())
......@@ -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)