diff --git a/data.c b/data.c
index 1557f6d..e8c67ca 100644
--- a/data.c
+++ b/data.c
@@ -948,6 +948,9 @@ _sch_xml_to_gnode (_sch_xml_to_gnode_parms *_parms, sch_node * schema, sch_ns *n
/* Want one field in list element for one or more entries */
APTERYX_NODE (node, g_strdup ((const char *) child->name));
DEBUG ("%*s%s\n", (depth + 1) * 2, " ", child->name);
+ sch_node *child_schema = sch_ns_node_child (ns, schema, (char *) child->name);
+ if (child_schema && rschema)
+ *rschema = child_schema;
}
break;
}
@@ -956,6 +959,9 @@ _sch_xml_to_gnode (_sch_xml_to_gnode_parms *_parms, sch_node * schema, sch_ns *n
{
GNode *_node = APTERYX_NODE (node, g_strdup ((const char *) child->name));
DEBUG ("%*s%s\n", depth * 2, " ", APTERYX_NAME (node));
+ sch_node *child_schema = sch_ns_node_child (ns, schema, (char *) child->name);
+ if (child_schema && rschema)
+ *rschema = child_schema;
if (!_parms->in_is_edit)
g_node_prepend_data (_node, NULL);
}
diff --git a/netconf.c b/netconf.c
index 5b79435..11a9a98 100644
--- a/netconf.c
+++ b/netconf.c
@@ -50,6 +50,12 @@ static struct _running_ds_lock_t
gboolean locked;
} running_ds_lock;
+typedef struct _q_param
+{
+ GNode *deepest_leaf;
+ int depth;
+} q_param;
+
#define NETCONF_BASE_1_0_END "]]>]]>"
#define NETCONF_BASE_1_1_END "\n##\n"
#define NETCONF_HELLO_END "hello>]]>]]>"
@@ -897,12 +903,36 @@ get_full_tree ()
}
static gboolean
-remove_null_data (GNode *node, gpointer data)
+process_subtree_query_leaves (GNode *node, gpointer data)
{
- if (node->data == NULL)
+ GNode *qnode;
+ q_param *qparam = (q_param *) data;
+ int depth = 0;
+
+ /* Subtree queries have null or value terminated trees */
+ node = node->parent;
+
+ if (node)
{
- g_node_unlink (node);
- g_node_destroy (node);
+ qnode = node;
+ while (qnode->parent)
+ {
+ qnode = qnode->parent;
+ depth++;
+ }
+ depth++;
+
+ if (g_strcmp0 (APTERYX_NAME(node), "*") == 0)
+ {
+ qparam->deepest_leaf = node;
+ qparam->depth = depth;
+ return true;
+ }
+ else if (depth > qparam->depth)
+ {
+ qparam->deepest_leaf = node;
+ qparam->depth = depth;
+ }
}
return false;
}
@@ -1235,76 +1265,72 @@ check_namespace_set (xmlNode *node, char **ns_href, char **ns_prefix)
return schema_path;
}
-static gpointer
-copy_node_data (gconstpointer src, gpointer dummy)
-{
- char *data = g_strdup (src);
-
- return data;
-}
-
-static void
-_merge_gnode_nodes (GNode *node1, GNode *node2)
+/* Getting the response node with netconf is more complicated than restconf as they can have multiple nodes at
+ * some levels. This routine uses the qnode and works upward to guide the tree node as it works from the top down */
+static GNode*
+get_response_node (GNode *tree, int rdepth, GNode *qnode, sch_node **rschema)
{
- GNode *child1;
- GNode *child2;
- GNode *copy;
+ GNode *rnode = tree;
+ GNode *child = NULL;
+ int depth = rdepth;
- for (child1 = node1->children; child1; child1 = child1->next)
+ while (--depth && rnode)
{
- /* Match child1 to a child of node2. If matched descend down the tree. */
- for (child2 = node2->children; child2; child2 = child2->next)
+ GNode *children = rnode->children;
+
+ if (children)
{
- if (g_strcmp0 (APTERYX_NAME (child2), "*") != 0 &&
- g_strcmp0 (APTERYX_NAME (child1), APTERYX_NAME (child2)) == 0)
+ GNode *pqnode = qnode;
+ int prdepth = depth;
+ while (pqnode && prdepth > 1)
{
- _merge_gnode_nodes (child1, child2);
- break;
+ if (pqnode->parent)
+ pqnode = pqnode->parent;
+ prdepth--;
}
- }
- }
- for (child2 = node2->children; child2; child2 = child2->next)
- {
- if (g_strcmp0 (APTERYX_NAME (child2), "*") == 0)
- continue;
-
- /* Match child2 to a child of node1. If not matched add the child2 tree
- * to the parent node1. */
- for (child1 = node1->children; child1; child1 = child1->next)
- {
- if (g_strcmp0 (APTERYX_NAME (child1), APTERYX_NAME (child2)) == 0)
+ if (g_strcmp0 (APTERYX_NAME (pqnode), "*") == 0)
break;
- }
- if (!child1)
- {
- copy = g_node_copy_deep (child2, copy_node_data, NULL);
- g_node_append (node1, copy);
- }
- }
-}
+ if (pqnode->parent)
+ {
+ for (child = children; child; child = child->next)
+ {
+ if (g_strcmp0 (APTERYX_NAME (child), APTERYX_NAME (pqnode)) == 0)
+ {
+ break;
+ }
+ }
-/* Merge tree2 into tree1. This is done by copying any part of the tree2 that is not in
- * tree1 into tree1. Tree2 is deleted at the end of the merge. */
-static void
-merge_gnode_trees (GNode *tree1, GNode *tree2)
-{
- if (tree1 && tree2)
- {
- _merge_gnode_nodes (tree1, tree2);
- apteryx_free_tree (tree2);
+ /* If the original query response does not have this part of the query
+ * add it to the response */
+ if (!child)
+ {
+ child = g_node_append_data (rnode, g_strdup (APTERYX_NAME (pqnode)));
+ char *path = apteryx_node_path(child);
+ sch_node *snode = sch_lookup (g_schema, path);
+ if (snode)
+ *rschema = snode;
+ g_free (path);
+ }
+ }
+ if (child)
+ rnode = child;
+ else
+ rnode = rnode->children;
+ }
}
+ return rnode;
}
static bool
get_query_to_xml (struct netconf_session *session, xmlNode *rpc, GNode *query,
- int rdepth, char *path, char **ns_href,
+ GNode *qnode, int qdepth, char *path, char **ns_href,
char **ns_prefix, xpath_type x_type, int schflags,
- bool is_subtree, bool is_filter, GList **xml_list)
+ bool is_subtree, bool is_filter, GList **xml_list,
+ sch_node *rschema, int rdepth)
{
GNode *tree = NULL;
- GNode *query_defaults = NULL;
xmlNode *xml = NULL;
/* Query database */
@@ -1334,31 +1360,21 @@ get_query_to_xml (struct netconf_session *session, xmlNode *rpc, GNode *query,
else if (!is_filter)
tree = get_full_tree ();
- if (schflags & SCH_F_ADD_DEFAULTS)
+ if (query && (schflags & SCH_F_ADD_DEFAULTS) && rschema)
{
+ GNode *rnode = NULL;
if (tree)
- {
- sch_traverse_tree (g_schema, NULL, tree, schflags | SCH_F_FILTER_RDEPTH, rdepth);
- query_defaults = query;
- g_node_traverse (query_defaults, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, remove_null_data, NULL);
- query = NULL;
- sch_traverse_tree (g_schema, NULL, query_defaults, schflags | SCH_F_FILTER_RDEPTH, rdepth);
+ rnode = get_response_node (tree, rdepth, qnode, &rschema);
- /* Merge the results of the search result tree with defaults and the query with defaults */
- merge_gnode_trees (tree, query_defaults);
- }
- else
- {
- /* Nothing in the database, but we may have defaults! */
- tree = query;
- g_node_traverse (tree, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, remove_null_data, NULL);
- query = NULL;
- sch_traverse_tree (g_schema, NULL, tree, schflags | SCH_F_FILTER_RDEPTH, rdepth);
- }
+ sch_add_defaults (g_schema, rschema, &tree, &query, rnode, qnode, rdepth,
+ qdepth, schflags);
}
- if (tree && (schflags & SCH_F_TRIM_DEFAULTS))
- sch_traverse_tree (g_schema, NULL, tree, schflags | SCH_F_FILTER_RDEPTH, rdepth);
+ if (tree && (schflags & SCH_F_TRIM_DEFAULTS) && rschema)
+ {
+ GNode *rnode = get_response_node (tree, rdepth, qnode, &rschema);
+ sch_traverse_tree (g_schema, rschema, rnode, schflags);
+ }
apteryx_free_tree (query);
@@ -1379,27 +1395,93 @@ get_query_schema (struct netconf_session *session, xmlNode *rpc, GNode *query,
int schflags, bool is_filter, bool is_subtree, GList **xml_list)
{
GNode *qnode = NULL;
+ sch_node *rschema = qschema;
int qdepth = 0;
+ int rdepth = 1;
+ int diff;
/* Get the depth of the response which is the depth of the query
- OR the up until the first path wildcard */
+ * or up until the first path wildcard */
qdepth = g_node_max_height (query);
- if (is_subtree && qdepth)
- qdepth--;
-
qnode = query;
- while (qnode &&
- g_node_n_children (qnode) == 1 &&
- g_strcmp0 (APTERYX_NAME (g_node_first_child (qnode)), "*") != 0)
+ if (is_subtree)
{
- qnode = g_node_first_child (qnode);
+ q_param qparam = { 0 };
+
+ /* Subtree queries end with a NULL data node or a value node, reduce the depth of the n-ary to compensate */
+ if (is_subtree)
+ {
+ if (qdepth)
+ qdepth--;
+ }
+ g_node_traverse (qnode, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, process_subtree_query_leaves, &qparam);
+ rdepth = qparam.depth;
+
+ if (qparam.deepest_leaf)
+ {
+ qnode = qparam.deepest_leaf;
+ if (g_strcmp0 (APTERYX_NAME (qparam.deepest_leaf), "*") == 0)
+ {
+ if (qdepth == rdepth)
+ rdepth--;
+ qdepth--;
+ if (qparam.deepest_leaf->parent)
+ qnode = qparam.deepest_leaf->parent;
+ }
+ }
+
+ if (qparam.deepest_leaf && sch_node_parent (rschema) && sch_is_list (sch_node_parent (rschema)))
+ {
+ /* We need to present the list rather than the key */
+ rschema = sch_node_parent (rschema);
+ if (rschema && qparam.deepest_leaf->parent)
+ {
+ char *s_name = sch_name (rschema);
+ if (qdepth >= rdepth && g_strcmp0 (s_name, APTERYX_NAME(qparam.deepest_leaf->parent)) != 0)
+ {
+ qdepth--;
+ rdepth--;
+ qnode = qparam.deepest_leaf->parent;
+ }
+ g_free (s_name);
+ }
+ }
+
+ diff = qdepth - rdepth;
+ while (diff--)
+ rschema = sch_node_parent (rschema);
+
+ if (qdepth != rdepth && sch_node_parent (rschema) && sch_is_list (sch_node_parent (rschema)))
+ {
+ /* We need to present the list rather than the key */
+ rschema = sch_node_parent (rschema);
+ rdepth--;
+ }
}
+ else
+ {
+ while (qnode &&
+ g_node_n_children (qnode) == 1 &&
+ g_strcmp0 (APTERYX_NAME (g_node_first_child (qnode)), "*") != 0)
+ {
+ qnode = g_node_first_child (qnode);
+ rdepth++;
+ }
- while (qnode && qnode->children && qnode->children->data)
- qnode = qnode->children;
+ diff = qdepth - rdepth;
+ while (diff--)
+ rschema = sch_node_parent (rschema);
- if (qdepth && qnode && g_strcmp0 (APTERYX_NAME (qnode), "*") == 0)
- qdepth--;
+ if (sch_node_parent (rschema) && sch_is_list (sch_node_parent (rschema)))
+ {
+ /* We need to present the list rather than the key */
+ rschema = sch_node_parent (rschema);
+ rdepth--;
+ }
+
+ while (qnode && qnode->children)
+ qnode = qnode->children;
+ }
/* Without a query we may need to add a wildcard to get everything from here down */
if (is_filter && qnode && qdepth == g_node_max_height (query) &&
@@ -1416,8 +1498,9 @@ get_query_schema (struct netconf_session *session, xmlNode *rpc, GNode *query,
}
}
- return get_query_to_xml (session, rpc, query, qdepth, path, ns_href,
- ns_prefix, x_type, schflags, is_subtree, true, xml_list);
+ return get_query_to_xml (session, rpc, query, qnode, qdepth, path, ns_href,
+ ns_prefix, x_type, schflags, is_subtree, true, xml_list,
+ rschema, rdepth);
}
static void
@@ -1559,8 +1642,8 @@ get_process_action (struct netconf_session *session, xmlNode *rpc, xmlNode *node
}
else if (!query && x_type == XPATH_EVALUATE)
{
- if (!get_query_to_xml (session, rpc, query, 0, path, &ns_href,
- &ns_prefix, x_type, schflags, false, true, xml_list))
+ if (!get_query_to_xml (session, rpc, query, NULL, 0, path, &ns_href,
+ &ns_prefix, x_type, schflags, false, true, xml_list, NULL, 0))
{
cleanup_on_xpath_error (session, attr, split, ns_href, ns_prefix, path);
return -1;
@@ -1730,8 +1813,8 @@ handle_get (struct netconf_session *session, xmlNode * rpc, gboolean config_only
/* Catch for get without filter */
if (!filter_seen && !xml_list)
{
- if (!get_query_to_xml (session, rpc, NULL, 0, NULL, NULL, NULL,
- XPATH_NONE, schflags, false, false, &xml_list))
+ if (!get_query_to_xml (session, rpc, NULL, NULL, 0, NULL, NULL, NULL,
+ XPATH_NONE, schflags, false, false, &xml_list, NULL, 0))
{
session->counters.in_bad_rpcs++;
netconf_global_stats.session_totals.in_bad_rpcs++;
diff --git a/tests/test_with_defaults.py b/tests/test_with_defaults.py
index 38cedbe..0411d5f 100644
--- a/tests/test_with_defaults.py
+++ b/tests/test_with_defaults.py
@@ -222,20 +222,60 @@ def test_with_defaults_report_all_subtree():
_get_test_with_defaults_and_filter(select, with_defaults, expected)
-# Test case where a query fails returns no result and the query itself is used to fill in defaults
-# Note - because the query is used as the basis of the result tree, the normal eth4
-# nodes are not present in the output.
+def test_with_defaults_report_all_subtree_list():
+ with_defaults = 'report-all'
+ select = ''
+ expected = """
+
+
+
+ eth0
+ 8192
+ up
+
+
+ eth1
+ 1500
+ up
+
+
+ eth2
+ 9000
+ not feeling so good
+
+
+ eth3
+ 1500
+ waking up
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
+# Test case where a query fails and returns no result. This is a specific request for a list item.
+# As the list item does not exist no data is returned.
def test_with_defaults_report_all_subtree_no_match():
with_defaults = 'report-all'
select = 'eth4'
expected = """
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
+def test_with_defaults_report_all_leaf():
+ apteryx.set("/test/settings/debug", "")
+ with_defaults = 'report-all'
+ select = ''
+ expected = """
-
-
- 1500
- up
-
-
+
+
+ disable
+
+
"""
_get_test_with_defaults_and_filter(select, with_defaults, expected)
@@ -288,13 +328,13 @@ def test_with_defaults_get_subtree_select_one_node_other():
select = 'cat'
expected = """
-
-
-
- cat
-
-
-
+
+
+
+ cat
+
+
+
"""
_get_test_with_defaults_and_filter(select, with_defaults, expected)
@@ -316,6 +356,98 @@ def test_with_default_report_all_get_leaf():
_get_test_with_defaults_and_filter(select, with_defaults, expected)
+def test_with_default_report_all_get_missing_leaf():
+ with_defaults = 'report-all'
+ select = 'eth1'
+ expected = """
+
+
+
+ eth1
+ 1500
+ up
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
+def test_with_default_report_all_get_specific_leaf():
+ with_defaults = 'report-all'
+ select = 'eth1'
+ expected = """
+
+
+
+ eth1
+ up
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
+def test_with_default_report_all_no_name_get_specific_leaf():
+ with_defaults = 'report-all'
+ select = ''
+ expected = """
+
+
+
+ eth0
+ 8192
+
+
+ eth1
+ 1500
+
+
+ eth2
+ 9000
+
+
+ eth3
+ 1500
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
+def test_with_default_report_all_no_defaults_set_leaf():
+ apteryx.set("/interfaces/interface/eth0/mtu", "")
+ apteryx.set("/interfaces/interface/eth2/mtu", "")
+ apteryx.set("/interfaces/interface/eth3/mtu", "")
+ with_defaults = 'report-all'
+ select = ''
+ expected = """
+
+
+
+ eth0
+ 1500
+
+
+ eth1
+ 1500
+
+
+ eth2
+ 1500
+
+
+ eth3
+ 1500
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
def test_with_default_report_all_get_leaf_different_depths():
with_defaults = 'report-all'
select = '''
@@ -443,6 +575,47 @@ def test_with_default_report_all_on_empty_branch():
_get_test_with_defaults_and_filter(select, with_defaults, expected)
+def test_with_default_report_all_trunk_data():
+ apteryx.set("/test/settings/users/alfred/name", "alfred")
+ apteryx.set("/test/settings/users/alfred/age", "87")
+ apteryx.set("/test/settings/debug", "")
+ with_defaults = 'report-all'
+ select = '''
+
+
+
+ '''
+ expected = """
+
+
+
+ disable
+ true
+ 1
+ yes
+
+
+ alfred
+ 87
+ false
+
+
+ bob
+ 34
+ true
+ 2
+ 23
+
+ 1
+
+
+
+ """
+ _get_test_with_defaults_and_filter(select, with_defaults, expected)
+
+
def test_with_default_report_all_proxy_get_leaf():
apteryx.set("/logical-elements/logical-element/loop/name", "loopy")
apteryx.set("/logical-elements/logical-element/loop/root", "root")