Browse Source

[trac926] add named map support for named map

also a small fix in lists
Jelte Jansen 14 years ago
parent
commit
7cc84b1bfe

+ 6 - 5
src/bin/bindctl/bindcmd.py

@@ -400,9 +400,8 @@ class BindCmdInterpreter(Cmd):
                 print("Error: " + str(dnfe))
             except isc.cc.data.DataAlreadyPresentError as dnfe:
                 print("Error: " + str(dnfe))
-            # [XX] TODO: add back
-            #except KeyError as ke:
-            #    print("Error: missing " + str(ke))
+            except KeyError as ke:
+                print("Error: missing " + str(ke))
         else:
             self.apply_cmd(cmd)
 
@@ -629,7 +628,7 @@ class BindCmdInterpreter(Cmd):
             values = self.config_data.get_value_maps(identifier, show_all)
             for value_map in values:
                 line = value_map['name']
-                if value_map['type'] in [ 'module', 'map' ]:
+                if value_map['type'] in [ 'module', 'map', 'named_map' ]:
                     line += "/"
                 elif value_map['type'] == 'list' \
                      and value_map['value'] != []:
@@ -637,7 +636,9 @@ class BindCmdInterpreter(Cmd):
                     # we have more data to show
                     line += "/"
                 else:
-                    line += "\t" + json.dumps(value_map['value'])
+                    # if type is named_map, don't print value
+                    if value_map['type'] != 'named_map':
+                        line += "\t" + json.dumps(value_map['value'])
                 line += "\t" + value_map['type']
                 line += "\t"
                 if value_map['default']:

+ 13 - 9
src/lib/python/isc/config/ccsession.py

@@ -444,7 +444,7 @@ class UIModuleCCSession(MultiConfigData):
         else:
             raise isc.cc.data.DataAlreadyPresentError(value + " already in " + identifier)
 
-    def _add_value_to_named_map(self, identifier, value):
+    def _add_value_to_named_map(self, identifier, value, item_value):
         if value is None:
             raise isc.cc.data.DataNotFoundError("Need a name to add a new item to named_map " + str(identifier))
         elif type(value) != str:
@@ -454,7 +454,7 @@ class UIModuleCCSession(MultiConfigData):
             if not cur_map:
                 cur_map = {}
             if value not in cur_map:
-                cur_map[value] = {}
+                cur_map[value] = item_value
                 self.set_value(identifier, cur_map)
             else:
                 raise isc.cc.data.DataAlreadyPresentError(value + " already in " + identifier)
@@ -481,7 +481,10 @@ class UIModuleCCSession(MultiConfigData):
         if 'list_item_spec' in module_spec:
             self._add_value_to_list(identifier, value)
         elif 'named_map_item_spec' in module_spec:
-            self._add_value_to_named_map(identifier, value)
+            item_value = None
+            if 'item_default' in module_spec['named_map_item_spec']:
+                item_value = module_spec['named_map_item_spec']['item_default']
+            self._add_value_to_named_map(identifier, value, item_value)
         else:
             raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named map")
 
@@ -516,11 +519,11 @@ class UIModuleCCSession(MultiConfigData):
                 raise isc.cc.data.DataNotFoundError(value + " not found in named_map " + str(identifier))
 
     def remove_value(self, identifier, value_str):
-        """Remove a value from a configuration list. The value string
-           must be a string representation of the full item. Raises
-           a DataTypeError if the value at the identifier is not a list,
-           or if the given value_str does not match the list_item_spec
-           """
+        """Remove a value from a configuration list or named map.
+        The value string must be a string representation of the full
+        item. Raises a DataTypeError if the value at the identifier
+        is not a list, or if the given value_str does not match the
+        list_item_spec """
         module_spec = self.find_spec_part(identifier)
         if module_spec is None:
             raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
@@ -528,9 +531,10 @@ class UIModuleCCSession(MultiConfigData):
         value = None
         if value_str is not None:
             value = isc.cc.data.parse_value_str(value_str)
-            isc.config.config_data.check_type(module_spec, [value])
 
         if 'list_item_spec' in module_spec:
+            if value is not None:
+                isc.config.config_data.check_type(module_spec['list_item_spec'], value)
             self._remove_value_from_list(identifier, value)
         elif 'named_map_item_spec' in module_spec:
             self._remove_value_from_named_map(identifier, value)

+ 61 - 2
src/lib/python/isc/config/config_data.py

@@ -145,6 +145,8 @@ def _find_spec_part_single(cur_spec, id_part):
             return cur_spec['list_item_spec']
         # not found
         raise isc.cc.data.DataNotFoundError(id + " not found")
+    elif type(cur_spec) == dict and 'named_map_item_spec' in cur_spec.keys():
+        return cur_spec['named_map_item_spec']
     elif type(cur_spec) == list:
         for cur_spec_item in cur_spec:
             if cur_spec_item['item_name'] == id:
@@ -408,11 +410,38 @@ class MultiConfigData:
             id_parts = isc.cc.data.split_identifier(id)
             id_prefix = ""
             while len(id_parts) > 0:
+                # [XX] TODO: Refactor
                 id_part = id_parts.pop(0)
                 item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
                 id_list = module + "/" + id_prefix + "/" + item_id
                 id_prefix += "/" + id_part
-                if list_indices is not None:
+                part_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
+                if part_spec['item_type'] == 'named_map':
+                    if len(id_parts) == 0:
+                        if 'item_default' in part_spec:
+                            return part_spec['item_default']
+                        else:
+                            return None
+                    id_part = id_parts.pop(0)
+
+                    named_map_value, type = self.get_value(id_list)
+                    if id_part in named_map_value:
+                        if len(id_parts) > 0:
+                            # we are looking for the *default* value.
+                            # so if not present in here, we need to
+                            # lookup the one from the spec
+                            rest_of_id = "/".join(id_parts)
+                            result = isc.cc.data.find_no_exc(named_map_value[id_part], rest_of_id)
+                            if result is None:
+                                spec_part = self.find_spec_part(identifier)
+                                if 'item_default' in spec_part:
+                                    return spec_part['item_default']
+                            return result
+                        else:
+                            return named_map_value[id_part]
+                    else:
+                        return None
+                elif list_indices is not None:
                     # there's actually two kinds of default here for
                     # lists; they can have a default value (like an
                     # empty list), but their elements can  also have
@@ -449,7 +478,12 @@ class MultiConfigData:
                     
             spec = find_spec_part(self._specifications[module].get_config_spec(), id)
             if 'item_default' in spec:
-                return spec['item_default']
+                # one special case, named_map
+                if spec['item_type'] == 'named_map':
+                    print("is " + id_part + " in named map?")
+                    return spec['item_default']
+                else:
+                    return spec['item_default']
             else:
                 return None
 
@@ -509,12 +543,37 @@ class MultiConfigData:
                         for i in range(len(list_value)):
                             self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
             elif item_type == "map":
+                value, status = self.get_value(identifier)
                 # just show the specific contents of a map, we are
                 # almost never interested in just its name
                 spec_part_map = spec_part['map_item_spec']
                 self._append_value_item(result, spec_part_map, identifier, all)
+            elif item_type == "named_map":
+                value, status = self.get_value(identifier)
+                if status == self.NONE or (status == self.DEFAULT and value == {}):
+                    raise isc.cc.data.DataNotFoundError(identifier)
+                # show just the one entry, when either the map is empty,
+                # or when this is element is not requested specifically
+                if (len(value.keys()) == 0 and (all or first)):
+                    entry = _create_value_map_entry(identifier,
+                                                    item_type,
+                                                    {}, status)
+                    result.append(entry)
+                elif not first and not all:
+                    entry = _create_value_map_entry(identifier,
+                                                    item_type,
+                                                    None, status)
+                    result.append(entry)
+                else:
+                    spec_part_named_map = spec_part['named_map_item_spec']
+                    for entry in value:
+                    #    xxxxxxxxxxx
+                        self._append_value_item(result, spec_part_named_map, identifier + "/" + entry, all)
             else:
                 value, status = self.get_value(identifier)
+                if status == self.NONE and not spec_part['item_optional']:
+                    raise isc.cc.data.DataNotFoundError(identifier)
+
                 entry = _create_value_map_entry(identifier,
                                                 item_type,
                                                 value, status)

+ 33 - 1
src/lib/python/isc/config/tests/ccsession_test.py

@@ -691,6 +691,12 @@ class TestUIModuleCCSession(unittest.TestCase):
         fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
         return UIModuleCCSession(fake_conn)
 
+    def create_uccs_named_map(self, fake_conn):
+        module_spec = isc.config.module_spec_from_file(self.spec_file("spec32.spec"))
+        fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()})
+        fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
+        return UIModuleCCSession(fake_conn)
+
     def test_init(self):
         fake_conn = fakeUIConn()
         fake_conn.set_get_answer('/module_spec', {})
@@ -711,12 +717,14 @@ class TestUIModuleCCSession(unittest.TestCase):
     def test_add_remove_value(self):
         fake_conn = fakeUIConn()
         uccs = self.create_uccs2(fake_conn)
+
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec2/item1", "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, 1, "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "no_such_item", "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec2/item1", "a")
+
         self.assertEqual({}, uccs._local_changes)
         uccs.add_value("Spec2/item5", "foo")
         self.assertEqual({'Spec2': {'item5': ['a', 'b', 'foo']}}, uccs._local_changes)
@@ -726,11 +734,35 @@ class TestUIModuleCCSession(unittest.TestCase):
         uccs.remove_value("Spec2/item5", "foo")
         uccs.add_value("Spec2/item5", "foo")
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
-        uccs.add_value("Spec2/item5", "foo")
+        self.assertRaises(isc.cc.data.DataAlreadyPresentError,
+                          uccs.add_value, "Spec2/item5", "foo")
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          uccs.remove_value, "Spec2/item5[123]", None)
         uccs.remove_value("Spec2/item5[0]", None)
         self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
 
+    def test_add_remove_value_named_map(self):
+        fake_conn = fakeUIConn()
+        uccs = self.create_uccs_named_map(fake_conn)
+        value, status = uccs.get_value("/Spec32/named_map_item")
+        self.assertEqual({'a': 1, 'b': 2}, value)
+        uccs.add_value("/Spec32/named_map_item", "foo")
+        value, status = uccs.get_value("/Spec32/named_map_item")
+        self.assertEqual({'a': 1, 'b': 2, 'foo': 3}, value)
+        uccs.set_value("/Spec32/named_map_item/bar", 4)
+        value, status = uccs.get_value("/Spec32/named_map_item")
+        self.assertEqual({'a': 1, 'b': 2, 'foo': 3, 'bar': 4}, value)
+
+        uccs.remove_value("/Spec32/named_map_item", "a")
+        uccs.remove_value("/Spec32/named_map_item", "foo")
+        value, status = uccs.get_value("/Spec32/named_map_item")
+        self.assertEqual({'b': 2, 'bar': 4}, value)
+
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          uccs.remove_value, "/Spec32/named_map_item",
+                          "no_such_item")
+
     def test_commit(self):
         fake_conn = fakeUIConn()
         uccs = self.create_uccs2(fake_conn)

+ 36 - 0
src/lib/python/isc/config/tests/config_data_test.py

@@ -236,6 +236,7 @@ class TestConfigData(unittest.TestCase):
         value, default = self.cd.get_value("item6/value2")
         self.assertEqual(None, value)
         self.assertEqual(False, default)
+        self.assertRaises(isc.cc.data.DataNotFoundError, self.cd.get_value, "item6/no_such_item")
 
     def test_get_default_value(self):
         self.assertEqual(1, self.cd.get_default_value("item1"))
@@ -410,6 +411,7 @@ class TestMultiConfigData(unittest.TestCase):
         self.assertEqual('a', value)
         value = self.mcd.get_default_value("Spec2/item5[1]")
         self.assertEqual('b', value)
+        self.assertRaises(self.mcd.get_default_value("Spec2/item5[2]"))
         value = self.mcd.get_default_value("Spec2/item5[5]")
         self.assertEqual(None, value)
         value = self.mcd.get_default_value("Spec2/item5[0][1]")
@@ -421,6 +423,17 @@ class TestMultiConfigData(unittest.TestCase):
         value = self.mcd.get_default_value("Spec2/no_such_item/asdf")
         self.assertEqual(None, value)
 
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
+        self.mcd.set_specification(module_spec)
+        value = self.mcd.get_default_value("Spec32/named_map_item")
+        self.assertEqual({ 'a': 1, 'b': 2}, value)
+        value = self.mcd.get_default_value("Spec32/named_map_item/a")
+        self.assertEqual(1, value)
+        value = self.mcd.get_default_value("Spec32/named_map_item/b")
+        self.assertEqual(2, value)
+        value = self.mcd.get_default_value("Spec32/named_map_item/no_such_item")
+        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)
@@ -544,6 +557,29 @@ class TestMultiConfigData(unittest.TestCase):
         maps = self.mcd.get_value_maps("/Spec22/value9")
         self.assertEqual(expected, maps)
 
+    def test_get_value_maps_named_map(self):
+        module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
+        self.mcd.set_specification(module_spec)
+        maps = self.mcd.get_value_maps()
+        self.assertEqual([{'default': False, 'type': 'module',
+                           'name': 'Spec32', 'value': None,
+                           'modified': False}], maps)
+        maps = self.mcd.get_value_maps("/Spec32/named_map_item")
+        self.assertEqual([{'default': True, 'type': 'integer',
+                           'name': 'Spec32/named_map_item/a',
+                           'value': 1, 'modified': False},
+                          {'default': True, 'type': 'integer',
+                           'name': 'Spec32/named_map_item/b',
+                           'value': 2, 'modified': False}], maps)
+        maps = self.mcd.get_value_maps("/Spec32/named_map_item/a")
+        self.assertEqual([{'default': True, 'type': 'integer',
+                           'name': 'Spec32/named_map_item/a',
+                           'value': 1, 'modified': False}], maps)
+        maps = self.mcd.get_value_maps("/Spec32/named_map_item/b")
+        self.assertEqual([{'default': True, 'type': 'integer',
+                           'name': 'Spec32/named_map_item/b',
+                           'value': 2, '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)