Fixed: [Change CLICON_NETCONF_DUPLICATE_ALLOW to remove duplicates](https://github.com/clicon/clixon-controller/issues/160)

C-API: Removed xml_tree_prune_flagged
This commit is contained in:
Olof hagsand 2024-11-23 10:51:04 +01:00
parent 68e132c275
commit 0193186272
11 changed files with 395 additions and 104 deletions

View file

@ -25,6 +25,7 @@ Expected: January 2025
* New `system-only-config` extension * New `system-only-config` extension
* New `ca_system_only` backend callback for reading system-only data * New `ca_system_only` backend callback for reading system-only data
* New `clixon-config@2024-11-01.yang` revision * New `clixon-config@2024-11-01.yang` revision
* Changed: `CLICON_NETCONF_DUPLICATE_ALLOW` to not only check but remove duplicates
* Added: `CLICON_XMLDB_SYSTEM_ONLY_CONFIG` * Added: `CLICON_XMLDB_SYSTEM_ONLY_CONFIG`
### C/CLI-API changes on existing features ### C/CLI-API changes on existing features
@ -37,6 +38,7 @@ Developers may need to change their code
### Corrected Bugs ### Corrected Bugs
* Fixed: [Change CLICON_NETCONF_DUPLICATE_ALLOW to remove duplicates](https://github.com/clicon/clixon-controller/issues/160)
* Fixed: Segv in canonical xpath transform * Fixed: Segv in canonical xpath transform
* Fixed: [Error with submodules and feature Interaction](https://github.com/clicon/clixon-controller/issues/158) * Fixed: [Error with submodules and feature Interaction](https://github.com/clicon/clixon-controller/issues/158)
* Fixed: [Expansion removes the double quote](https://github.com/clicon/clixon/issues/524) * Fixed: [Expansion removes the double quote](https://github.com/clicon/clixon/issues/524)

View file

@ -657,10 +657,20 @@ from_client_edit_config(clixon_handle h,
*/ */
if ((ret = xml_yang_validate_minmax(xc, 1, &xret)) < 0) if ((ret = xml_yang_validate_minmax(xc, 1, &xret)) < 0)
goto done; goto done;
/* Disable duplicate check in NETCONF messages.*/ if (ret == 0){
if (clicon_option_bool(h, "CLICON_NETCONF_DUPLICATE_ALLOW")) if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
; goto done;
else if (ret == 1 && (ret = xml_yang_validate_unique_recurse(xc, &xret)) < 0) goto ok;
}
/* Must do before duplicate check */
if (xml_sort_recurse(xc) < 0)
goto done;
/* Disable duplicate check in NETCONF messages. */
if (clicon_option_bool(h, "CLICON_NETCONF_DUPLICATE_ALLOW")){
if ((ret = xml_duplicate_remove_recurse(xc, &xret)) < 0)
goto done;
}
else if ((ret = xml_yang_validate_unique_recurse(xc, &xret)) < 0)
goto done; goto done;
/* xmldb_put (difflist handling) requires list keys */ /* xmldb_put (difflist handling) requires list keys */
if (ret == 1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0) if (ret == 1 && (ret = xml_yang_validate_list_key_only(xc, &xret)) < 0)
@ -670,11 +680,6 @@ from_client_edit_config(clixon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* Cant do this earlier since we dont have a yang spec to
* the upper part of the tree, until we get the "config" tree.
*/
if (xml_sort_recurse(xc) < 0)
goto done;
if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){ if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){
if (netconf_operation_failed(cbret, "protocol", clixon_err_reason())< 0) if (netconf_operation_failed(cbret, "protocol", clixon_err_reason())< 0)
goto done; goto done;

View file

@ -312,9 +312,8 @@ startup_common(clixon_handle h,
clixon_err(OE_XML, EFAULT, "Yang sort error"); clixon_err(OE_XML, EFAULT, "Yang sort error");
} }
/* clear XML tree of defaults */ /* clear XML tree of defaults */
if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0) if (xml_tree_prune_flags(xt, XML_FLAG_DEFAULT, XML_FLAG_DEFAULT) < 0)
goto done; goto done;
if (xmldb_dump(h, stdout, xt, FORMAT_XML, if (xmldb_dump(h, stdout, xt, FORMAT_XML,
clicon_option_bool(h, "CLICON_XMLDB_PRETTY"), clicon_option_bool(h, "CLICON_XMLDB_PRETTY"),
WITHDEFAULTS_REPORT_ALL, 0, NULL) < 0) WITHDEFAULTS_REPORT_ALL, 0, NULL) < 0)

View file

@ -47,5 +47,6 @@ int xml_yang_validate_minmax(cxobj *xt, int presence, cxobj **xret);
int xml_yang_validate_minmax_recurse(cxobj *xt, cxobj **xret); int xml_yang_validate_minmax_recurse(cxobj *xt, cxobj **xret);
int xml_yang_validate_unique(cxobj *xt, cxobj **xret); int xml_yang_validate_unique(cxobj *xt, cxobj **xret);
int xml_yang_validate_unique_recurse(cxobj *xt, cxobj **xret); int xml_yang_validate_unique_recurse(cxobj *xt, cxobj **xret);
int xml_duplicate_remove_recurse(cxobj *xt, cxobj **xret);
#endif /* _CLIXON_VALIDATE_MINMAX_H_ */ #endif /* _CLIXON_VALIDATE_MINMAX_H_ */

View file

@ -68,8 +68,8 @@ int xml_diff(cxobj *x0, cxobj *x1,
cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen); cxobj ***changed_x0, cxobj ***changed_x1, int *changedlen);
int xml_tree_equal(cxobj *x0, cxobj *x1); int xml_tree_equal(cxobj *x0, cxobj *x1);
int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark);
int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
int xml_tree_prune_flags(cxobj *xt, int flags, int mask); int xml_tree_prune_flags(cxobj *xt, int flags, int mask);
int xml_tree_prune_flags1(cxobj *xt, int flags, int mask, int recurse, int *removed);
int xml_namespace_change(cxobj *x, char *ns, char *prefix); int xml_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_sanity(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, cxobj **xerr); int xml_non_config_data(cxobj *xt, cxobj **xerr);

View file

@ -79,7 +79,7 @@
#include "clixon_xml_bind.h" #include "clixon_xml_bind.h"
#include "clixon_validate_minmax.h" #include "clixon_validate_minmax.h"
/*! New element last in list, check if already exists if sp return -1 /*! New element last in list, check if already exists if so return -1
* *
* @param[in] vec Vector of existing entries (new is last) * @param[in] vec Vector of existing entries (new is last)
* @param[in] i1 The new entry is placed at vec[i1] * @param[in] i1 The new entry is placed at vec[i1]
@ -139,19 +139,21 @@ unique_search_xpath(cxobj *x,
/*! New element last in list, return error if already exists /*! New element last in list, return error if already exists
* *
* @param[in] vec Vector of existing entries (new is last) * @param[in] vec Vector of existing entries (new is last)
* @param[in] i1 The new entry is placed at vec[i1] * @param[in] i1 The new entry is placed at vec[i1]
* @param[in] vlen Length of vec * @param[in] vlen Length of vec
* @param[in] sorted Sorted by system, ie sorted by key, otherwise no assumption * @param[in] sorted Sorted by system, ie sorted by key, otherwise no assumption
* @retval 0 OK, entry is unique * @param[out] dupl Index of duplicated element (if retval = -1)
* @retval -1 Duplicate detected * @retval 0 OK, entry is unique
* @retval -1 Duplicate detected
* @note This is currently quadratic complexity. It could be improved by inserting new element sorted and binary search. * @note This is currently quadratic complexity. It could be improved by inserting new element sorted and binary search.
*/ */
static int static int
check_insert_duplicate(char **vec, check_insert_duplicate(char **vec,
int i1, int i1,
int vlen, int vlen,
int sorted) int sorted,
int *dupl)
{ {
int i; int i;
int v; int v;
@ -168,6 +170,8 @@ check_insert_duplicate(char **vec,
return 0; return 0;
} }
/* here we have passed thru all keys of previous element and they are all equal */ /* here we have passed thru all keys of previous element and they are all equal */
if (dupl)
*dupl = i;
return -1; return -1;
} }
else{ else{
@ -180,7 +184,13 @@ check_insert_duplicate(char **vec,
if (v==vlen) /* duplicate */ if (v==vlen) /* duplicate */
break; break;
} }
return i==i1?0:-1; if (i<i1){
if (dupl)
*dupl = i;
return -1;
}
else
return 0;
} }
} }
@ -190,6 +200,7 @@ check_insert_duplicate(char **vec,
* @param[in] xt The parent of x (a list) * @param[in] xt The parent of x (a list)
* @param[in] y Its yang spec (Y_LIST) * @param[in] y Its yang spec (Y_LIST)
* @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys * @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys
* @param[in] mark If set, a flag mask to mark (prior) duplicate object with
* @param[out] xret Error XML tree. Free with xml_free after use * @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK * @retval 1 Validation OK
* @retval 0 Validation failed (cbret set) * @retval 0 Validation failed (cbret set)
@ -213,12 +224,14 @@ check_unique_list_direct(cxobj *x,
cxobj *xt, cxobj *xt,
yang_stmt *y, yang_stmt *y,
yang_stmt *yu, yang_stmt *yu,
int mark,
cxobj **xret) cxobj **xret)
{ {
int retval = -1; int retval = -1;
cg_var *cvi; /* unique node name */ cg_var *cvi; /* unique node name */
cxobj *xi; cxobj *xi;
char **vec = NULL; /* 2xmatrix */ char **vec = NULL; /* 2xmatrix */
cxobj **xvec = NULL;
int clen; int clen;
int i; int i;
int v; int v;
@ -226,6 +239,7 @@ check_unique_list_direct(cxobj *x,
int sorted; int sorted;
char *str; char *str;
cvec *cvk; cvec *cvk;
int dupl;
/* If list and is sorted by system, then it is assumed elements are in key-order which is optimized /* If list and is sorted by system, then it is assumed elements are in key-order which is optimized
* Other cases are "unique" constraint or list sorted by user which is quadratic in complexity * Other cases are "unique" constraint or list sorted by user which is quadratic in complexity
@ -239,15 +253,21 @@ check_unique_list_direct(cxobj *x,
/* No keys: no checks necessary */ /* No keys: no checks necessary */
goto ok; goto ok;
} }
/* x need not be child 0, which could make the vector larger than necessary */
if ((vec = calloc(clen*xml_child_nr(xt), sizeof(char*))) == NULL){ if ((vec = calloc(clen*xml_child_nr(xt), sizeof(char*))) == NULL){
clixon_err(OE_UNIX, errno, "calloc"); clixon_err(OE_UNIX, errno, "calloc");
goto done; goto done;
} }
if ((xvec = calloc(xml_child_nr(xt), sizeof(cxobj*))) == NULL){
clixon_err(OE_UNIX, errno, "calloc");
goto done;
}
/* A vector is built with key-values, for each iteration check "backward" in the vector /* A vector is built with key-values, for each iteration check "backward" in the vector
* for duplicates * for duplicates
*/ */
i = 0; /* x element index */ i = 0; /* x element index */
do { do {
xvec[i] = x;
cvi = NULL; cvi = NULL;
v = 0; /* index in each tuple */ v = 0; /* index in each tuple */
/* XXX Quadratic if clen > 1 */ /* XXX Quadratic if clen > 1 */
@ -267,7 +287,9 @@ check_unique_list_direct(cxobj *x,
} }
if (cvi==NULL){ if (cvi==NULL){
/* Last element (i) is newly inserted, see if it is already there */ /* Last element (i) is newly inserted, see if it is already there */
if (check_insert_duplicate(vec, i, clen, sorted) < 0){ if (check_insert_duplicate(vec, i, clen, sorted, &dupl) < 0){
if (mark)
xml_flag_set(xvec[dupl], mark);
if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0) if (xret && netconf_data_not_unique_xml(xret, x, cvk) < 0)
goto done; goto done;
goto fail; goto fail;
@ -280,6 +302,8 @@ check_unique_list_direct(cxobj *x,
/* It would be possible to cache vec here as an optimization */ /* It would be possible to cache vec here as an optimization */
retval = 1; retval = 1;
done: done:
if (xvec)
free(xvec);
if (vec) if (vec)
free(vec); free(vec);
return retval; return retval;
@ -294,6 +318,7 @@ check_unique_list_direct(cxobj *x,
* @param[in] xt The parent of x (a list) * @param[in] xt The parent of x (a list)
* @param[in] y Its yang spec (Y_LIST) * @param[in] y Its yang spec (Y_LIST)
* @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys * @param[in] yu A yang unique (Y_UNIQUE) for unique schema node ids or (Y_LIST) for list keys
* @param[in] mark If set, a flag mask to mark (prior) duplicate object with
* @param[out] xret Error XML tree. Free with xml_free after use * @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK * @retval 1 Validation OK
* @retval 0 Validation failed (xret set) * @retval 0 Validation failed (xret set)
@ -317,6 +342,7 @@ check_unique_list(cxobj *x,
cxobj *xt, cxobj *xt,
yang_stmt *y, yang_stmt *y,
yang_stmt *yu, yang_stmt *yu,
uint16_t mark,
cxobj **xret) cxobj **xret)
{ {
int retval = -1; int retval = -1;
@ -333,7 +359,7 @@ check_unique_list(cxobj *x,
/* Check if multiple direct children */ /* Check if multiple direct children */
cvk = yang_cvec_get(yu); cvk = yang_cvec_get(yu);
if (cvec_len(cvk) > 1){ if (cvec_len(cvk) > 1){
retval = check_unique_list_direct(x, xt, y, yu, xret); retval = check_unique_list_direct(x, xt, y, yu, mark, xret);
goto done; goto done;
} }
cvi = cvec_i(cvk, 0); cvi = cvec_i(cvk, 0);
@ -343,7 +369,7 @@ check_unique_list(cxobj *x,
} }
/* Check if direct schmeanode-id , ie not xpath */ /* Check if direct schmeanode-id , ie not xpath */
if (index(xpath0, '/') == NULL){ if (index(xpath0, '/') == NULL){
retval = check_unique_list_direct(x, xt, y, yu, xret); retval = check_unique_list_direct(x, xt, y, yu, mark, xret);
goto done; goto done;
} }
/* Here proper xpath with at least one slash (can there be a descendant schemanodeid w/o slash?) */ /* Here proper xpath with at least one slash (can there be a descendant schemanodeid w/o slash?) */
@ -484,6 +510,7 @@ check_empty_list_minmax(cxobj *xt,
* @param[in] x XML LIST node * @param[in] x XML LIST node
* @param[in] xt XML parent * @param[in] xt XML parent
* @param[in] y YANG of x * @param[in] y YANG of x
* @param[in] mark If set, a flag mask to mark (prior) duplicate object with
* @param[out] xret Error as XML if ret = 0 * @param[out] xret Error as XML if ret = 0
* @retval 1 Validation OK * @retval 1 Validation OK
* @retval 0 Validation failed (xret set) * @retval 0 Validation failed (xret set)
@ -493,6 +520,7 @@ static int
xml_yang_minmax_new_list(cxobj *x, xml_yang_minmax_new_list(cxobj *x,
cxobj *xt, cxobj *xt,
yang_stmt *y, yang_stmt *y,
int mark,
cxobj **xret) cxobj **xret)
{ {
int retval = -1; int retval = -1;
@ -503,7 +531,7 @@ xml_yang_minmax_new_list(cxobj *x,
/* Here new (first element) of lists only /* Here new (first element) of lists only
* First check unique keys direct children * First check unique keys direct children
*/ */
if ((ret = check_unique_list_direct(x, xt, y, y, xret)) < 0) if ((ret = check_unique_list_direct(x, xt, y, y, mark, xret)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -520,7 +548,7 @@ xml_yang_minmax_new_list(cxobj *x,
* 1) multiple direct children (no prefixes), eg "a b" * 1) multiple direct children (no prefixes), eg "a b"
* 2) single xpath with canonical prefixes, eg "/ex:a/ex:b" * 2) single xpath with canonical prefixes, eg "/ex:a/ex:b"
*/ */
if ((ret = check_unique_list(x, xt, y, yu, xret)) < 0) if ((ret = check_unique_list(x, xt, y, yu, 0, xret)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -538,6 +566,7 @@ xml_yang_minmax_new_list(cxobj *x,
* @param[in] x0 XML LIST node * @param[in] x0 XML LIST node
* @param[in] xt XML parent * @param[in] xt XML parent
* @param[in] y0 YANG of x0 * @param[in] y0 YANG of x0
* @param[in] mark If set, a flag mask to mark (prior) duplicate object with
* @param[out] xret Error as XML if ret = 0 * @param[out] xret Error as XML if ret = 0
* @retval 1 Validation OK * @retval 1 Validation OK
* @retval 0 Validation failed (xret set) * @retval 0 Validation failed (xret set)
@ -548,6 +577,7 @@ static int
xml_yang_minmax_new_leaf_list(cxobj *x0, xml_yang_minmax_new_leaf_list(cxobj *x0,
cxobj *xt, cxobj *xt,
yang_stmt *y0, yang_stmt *y0,
int mark,
cxobj **xret) cxobj **xret)
{ {
int retval = -1; int retval = -1;
@ -567,6 +597,8 @@ xml_yang_minmax_new_leaf_list(cxobj *x0,
if ((bj = xml_body(xj)) == NULL) if ((bj = xml_body(xj)) == NULL)
continue; continue;
if (bi && bj && strcmp(bi, bj) == 0){ if (bi && bj && strcmp(bi, bj) == 0){
if (mark)
xml_flag_set(xi, mark);
if ((cvv = cvec_new(0)) == NULL){ if ((cvv = cvec_new(0)) == NULL){
clixon_err(OE_UNIX, errno, "cvec_new"); clixon_err(OE_UNIX, errno, "cvec_new");
goto done; goto done;
@ -758,7 +790,7 @@ xml_yang_validate_minmax(cxobj *xt,
/* new list check */ /* new list check */
if (ret){ if (ret){
if (keyw == Y_LIST){ if (keyw == Y_LIST){
if ((ret = xml_yang_minmax_new_list(x, xt, y, xret)) < 0) if ((ret = xml_yang_minmax_new_list(x, xt, y, 0, xret)) < 0)
goto done; goto done;
} }
#ifdef NOTYET /* XXX This is enforced in xml_yang_validate_unique instead */ #ifdef NOTYET /* XXX This is enforced in xml_yang_validate_unique instead */
@ -926,13 +958,13 @@ xml_yang_validate_unique(cxobj *xt,
/* new list check */ /* new list check */
switch (keyw){ switch (keyw){
case Y_LIST: case Y_LIST:
if ((ret = xml_yang_minmax_new_list(x, xt, y, xret)) < 0) if ((ret = xml_yang_minmax_new_list(x, xt, y, 0, xret)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
break; break;
case Y_LEAF_LIST: case Y_LEAF_LIST:
if ((ret = xml_yang_minmax_new_leaf_list(x, xt, y, xret)) < 0) if ((ret = xml_yang_minmax_new_leaf_list(x, xt, y, 0, xret)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -1002,3 +1034,119 @@ xml_yang_validate_unique_recurse(cxobj *xt,
retval = 0; retval = 0;
goto done; goto done;
} }
/*! YANG unique check and remove duplicates, keep last
*
* Assume xt:s children are sorted and yang populated.
* @param[in] xt XML parent (may have lists w unique constraints as child)
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 2 Locally abort this subtree, continue with others
* @retval 1 Abort, dont continue with others, return 1 to end user
* @retval 0 OK, continue
* @retval -1 Error, aborted at first error encounter, return -1 to end user
* @see xml_yang_validate_minmax which include these unique tests
*/
static int
xml_duplicate_remove(cxobj *xt,
void *arg)
{
int retval = -1;
cxobj **xret = (cxobj **)arg;
cxobj *x;
yang_stmt *y;
yang_stmt *yprev;
enum rfc_6020 keyw;
int again;
int ret;
again = 1;
while (again){
again = 0;
yprev = NULL;
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
if ((y = xml_spec(x)) == NULL)
continue;
keyw = yang_keyword_get(y);
if (keyw == Y_LIST || keyw == Y_LEAF_LIST){
if (y == yprev){ /* equal: continue, assume list check does look-forward */
continue;
}
/* new list check */
switch (keyw){
case Y_LIST:
if ((ret = xml_yang_minmax_new_list(x, xt, y, XML_FLAG_DEL, xret)) < 0)
goto done;
if (ret == 0){
if (xml_tree_prune_flags1(xt, XML_FLAG_DEL, XML_FLAG_DEL, 0, &again) < 0)
goto done;
if (again){
if (xret && *xret){
xml_free(*xret);
*xret = NULL;
}
break;
}
goto fail;
}
break;
case Y_LEAF_LIST:
if ((ret = xml_yang_minmax_new_leaf_list(x, xt, y, XML_FLAG_DEL, xret)) < 0)
goto done;
if (ret == 0){
if (xml_tree_prune_flags1(xt, XML_FLAG_DEL, XML_FLAG_DEL, 0, &again) < 0)
goto done;
if (again){
if (xret && *xret){
xml_free(*xret);
*xret = NULL;
}
break;
}
goto fail;
}
break;
default:
break;
}
if (again)
break;
yprev = y;
}
}
}
retval = 0;
done:
return retval;
fail:
retval = 1;
goto done;
}
/*! Recursive YANG unique check and remove duplicates, keep last
*
* @param[in] xt XML parent (may have lists w unique constraints as child)
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
* @see xml_yang_validate_unique_recurse
*/
int
xml_duplicate_remove_recurse(cxobj *xt,
cxobj **xret)
{
int retval = -1;
int ret;
if ((ret = xml_apply0(xt, CX_ELMNT, xml_duplicate_remove, xret)) < 0)
goto done;
if (ret == 1)
goto fail;
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}

View file

@ -676,7 +676,7 @@ xml_tree_equal(cxobj *x0,
* xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL); * xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL);
* @endcode * @endcode
* @note This function seems a little too complex semantics * @note This function seems a little too complex semantics
* @see xml_tree_prune_flagged for a simpler variant * @see xml_tree_prune_flags1 for a simpler variant
*/ */
int int
xml_tree_prune_flagged_sub(cxobj *xt, xml_tree_prune_flagged_sub(cxobj *xt,
@ -750,46 +750,7 @@ xml_tree_prune_flagged_sub(cxobj *xt,
return retval; return retval;
} }
/*! Prune everything that passes test /*! Prune everything that passes test recursively
*
* @param[in] xt XML tree with some node marked
* @param[in] flag Which flag to test for
* @param[in] test 1: test that flag is set, 0: test that flag is not set
* @retval 0 OK
* @retval -1 Error
* The function removes all branches that does not pass test
* @code
* xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1);
* @endcode
*/
int
xml_tree_prune_flagged(cxobj *xt,
int flag,
int test)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
x = NULL;
xprev = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if (xml_flag(x, flag) == (test?flag:0)){ /* Pass test means purge */
if (xml_purge(x) < 0)
goto done;
x = xprev;
continue;
}
if (xml_tree_prune_flagged(x, flag, test) < 0)
goto done;
xprev = x;
}
retval = 0;
done:
return retval;
}
/*! Prune everything that passes test
* *
* @param[in] xt XML tree with some node marked * @param[in] xt XML tree with some node marked
* @param[in] flags Flags set * @param[in] flags Flags set
@ -800,15 +761,40 @@ xml_tree_prune_flagged(cxobj *xt,
* @code * @code
* xml_tree_prune_flags(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT); * xml_tree_prune_flags(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT);
* @endcode * @endcode
* @see xml_tree_prune_flags1
*/ */
int int
xml_tree_prune_flags(cxobj *xt, xml_tree_prune_flags(cxobj *xt,
int flags, int flags,
int mask) int mask)
{ {
int retval = -1; return xml_tree_prune_flags1(xt, flags, mask, 1, NULL);
cxobj *x; }
cxobj *xprev;
/*! Prune everything that passes test
*
* @param[in] xt XML tree with some node marked
* @param[in] flags Flags set
* @param[in] mask Which flags to test for
* @param[in] recurse 0: one level only, 1: recursive
* @param[out] removed Number of entities removed
* @retval 0 OK
* @retval -1 Error
* The function removes all branches that does pass test
* @code
* xml_tree_prune_flags1(xt, XML_FLAG_MARK, XML_FLAG_MARK|XML_FLAG_DEFAULT, 1);
* @endcode
*/
int
xml_tree_prune_flags1(cxobj *xt,
int flags,
int mask,
int recursive,
int *removed)
{
int retval = -1;
cxobj *x;
cxobj *xprev;
x = NULL; x = NULL;
xprev = NULL; xprev = NULL;
@ -817,10 +803,13 @@ xml_tree_prune_flags(cxobj *xt,
if (xml_purge(x) < 0) if (xml_purge(x) < 0)
goto done; goto done;
x = xprev; x = xprev;
if (removed)
(*removed)++;
continue; continue;
} }
if (xml_tree_prune_flags(x, flags, mask) < 0) if (recursive)
goto done; if (xml_tree_prune_flags1(x, flags, mask, 1, removed) < 0)
goto done;
xprev = x; xprev = x;
} }
retval = 0; retval = 0;
@ -828,7 +817,6 @@ xml_tree_prune_flags(cxobj *xt,
return retval; return retval;
} }
/*! Change namespace of XML node /*! Change namespace of XML node
* *
* @param[in] x XML node * @param[in] x XML node

View file

@ -144,7 +144,7 @@ new "minmax: minimal validate ok"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "minmax: maximal: 2x a1,b1" new "minmax: maximal: 2x a1,b1"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><a0><k>0</k></a0><a0><k>1</k></a0><a0><k>unbounded</k></a0><a1><k>0</k></a1><a1><k>1</k></a1><b0>0</b0><b0>1</b0><b0>unbounded</b0><b1>1</b1><b1>0</b1><b0>1</b0></c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"><a0><k>0</k></a0><a0><k>1</k></a0><a0><k>unbounded</k></a0><a1><k>0</k></a1><a1><k>1</k></a1><b0>0</b0><b0>1</b0><b0>unbounded</b0><b1>1</b1><b1>0</b1></c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "minmax: validate ok" new "minmax: validate ok"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"

View file

@ -2,6 +2,7 @@
# Detect duplicates in incoming edit-configs (not top-level) # Detect duplicates in incoming edit-configs (not top-level)
# Both list and leaf-list # Both list and leaf-list
# See https://github.com/clicon/clixon-controller/issues/107 # See https://github.com/clicon/clixon-controller/issues/107
# Also, check CLICON_NETCONF_DUPLICATE_ALLOW
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -31,22 +32,33 @@ module unique{
yang-version 1.1; yang-version 1.1;
namespace "urn:example:clixon"; namespace "urn:example:clixon";
prefix un; prefix un;
container c{ container c{
presence "trigger"; // force presence container to trigger error presence "trigger"; // force presence container to trigger error
list server { list server {
description ""; description "";
key "name"; key "name";
leaf name { leaf name {
type string; type string;
} }
leaf value { leaf value {
type string; type string;
} }
} }
leaf-list b{ list user {
type string; description "";
} ordered-by user;
} key "name";
leaf name {
type string;
}
leaf value {
type string;
}
}
leaf-list b{
type string;
}
}
} }
EOF EOF
@ -98,6 +110,22 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
</server> </server>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/server[name=\"one\"]/name</non-unique></error-info></rpc-error></rpc-reply>" </c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/server[name=\"one\"]/name</non-unique></error-info></rpc-error></rpc-reply>"
new "Add user sorted list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<user>
<name>one</name>
<value>foo</value>
</user>
<user>
<name>two</name>
<value>bar</value>
</user>
<user>
<name>one</name>
<value>foo</value>
</user>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/user[name=\"one\"]/name</non-unique></error-info></rpc-error></rpc-reply>"
new "Add leaf-list with duplicate" new "Add leaf-list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\"> expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<b>one</b> <b>one</b>
@ -105,6 +133,130 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
<b>one</b> <b>one</b>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/b[.=\"one\"]/one</non-unique></error-info></rpc-error></rpc-reply>" </c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/b[.=\"one\"]/one</non-unique></error-info></rpc-error></rpc-reply>"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
fi
# Check CLICON_NETCONF_DUPLICATE_ALLOW
if [ $BE -ne 0 ]; then
new "start backend -s init -f $cfg"
# start new backend
start_backend -s init -f $cfg -o CLICON_NETCONF_DUPLICATE_ALLOW=true
fi
new "Wait backend 2"
wait_backend
new "Add list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<server>
<name>bbb</name>
<value>foo</value>
</server>
<server>
<name>bbb</name>
<value>bar</value>
</server>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Check no duplicates"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><c xmlns=\"urn:example:clixon\"><server><name>bbb</name><value>bar</value></server></c></data></rpc-reply>"
new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Add complex list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<server>
<name>aaa</name>
<value>foo</value>
</server>
<server>
<name>bbb</name>
<value>foo</value>
</server>
<server>
<name>ccc</name>
<value>foo</value>
</server>
<server>
<name>bbb</name>
<value>bar</value>
</server>
<server>
<name>ccc</name>
<value>bar</value>
</server>
<server>
<name>bbb</name>
<value>fie</value>
</server>
<server>
<name>ddd</name>
<value>foo</value>
</server>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Check complex no duplicates"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><c xmlns=\"urn:example:clixon\"><server><name>aaa</name><value>foo</value></server><server><name>bbb</name><value>fie</value></server><server><name>ccc</name><value>bar</value></server><server><name>ddd</name><value>foo</value></server></c></data></rpc-reply>"
new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Add user sorted list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<user>
<name>aaa</name>
<value>foo</value>
</user>
<user>
<name>bbb</name>
<value>foo</value>
</user>
<user>
<name>ccc</name>
<value>foo</value>
</user>
<user>
<name>bbb</name>
<value>bar</value>
</user>
<user>
<name>ccc</name>
<value>bar</value>
</user>
<user>
<name>bbb</name>
<value>fie</value>
</user>
<user>
<name>ddd</name>
<value>foo</value>
</user>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Check user sorted no duplicates"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><c xmlns=\"urn:example:clixon\"><user><name>aaa</name><value>foo</value></user><user><name>ccc</name><value>bar</value></user><user><name>bbb</name><value>fie</value></user><user><name>ddd</name><value>foo</value></user></c></data></rpc-reply>"
new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Add leaf-list with duplicate"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns=\"urn:example:clixon\">
<b>aaa</b>
<b>aaa</b>
<b>bbb</b>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
new "Check leaf-list no duplicates"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>" "" "<rpc-reply $DEFAULTNS><data><c xmlns=\"urn:example:clixon\"><b>aaa</b><b>bbb</b></c></data></rpc-reply>"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"
# Check if premature kill # Check if premature kill

View file

@ -199,13 +199,7 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
<ip>192.0.2.1</ip> <ip>192.0.2.1</ip>
<port>25</port> <port>25</port>
</server> </server>
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>" </c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/server[name=\"smtp\"]/ip</non-unique><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/rpc/edit-config/config/c/server[name=\"smtp\"]/port</non-unique></error-info></rpc-error></rpc-reply>"
new "netconf validate (should fail) example3"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>" "" "<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-app-tag>data-not-unique</error-app-tag><error-severity>error</error-severity><error-info><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/c/server[name=\"smtp\"]/ip</non-unique><non-unique xmlns=\"urn:ietf:params:xml:ns:yang:1\">/c/server[name=\"smtp\"]/port</non-unique></error-info></rpc-error></rpc-reply>"
new "netconf discard-changes"
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"

View file

@ -53,6 +53,7 @@ module clixon-config {
description description
"Added options: "Added options:
CLICON_XMLDB_SYSTEM_ONLY_CONFIG CLICON_XMLDB_SYSTEM_ONLY_CONFIG
Changed: CLICON_NETCONF_DUPLICATE_ALLOW to not only check but remove duplicates
Released in Clixon 7.3"; Released in Clixon 7.3";
} }
revision 2024-08-01 { revision 2024-08-01 {
@ -72,7 +73,7 @@ module clixon-config {
CLICON_YANG_SCHEMA_MOUNT_SHARE: Share same YANGs of equal moint-points. CLICON_YANG_SCHEMA_MOUNT_SHARE: Share same YANGs of equal moint-points.
CLICON_SOCK_PRIO: Enable socket event priority CLICON_SOCK_PRIO: Enable socket event priority
CLICON_XMLDB_MULTI: Split datastore into multiple sub files CLICON_XMLDB_MULTI: Split datastore into multiple sub files
CLICON_CLI_OUTPUT_FORMAT: Default CLI output format CLICON_CLI_OUTPUT_FORMAT: Defauldirt CLI output format
CLICON_AUTOLOCK: Implicit locks CLICON_AUTOLOCK: Implicit locks
Released in Clixon 7.1"; Released in Clixon 7.1";
} }
@ -791,10 +792,11 @@ module clixon-config {
type boolean; type boolean;
default false; default false;
description description
"Disable duplicate check in NETCONF messages. "Remove duplicates in incoming NETCONF messages instead of signaling errors.
In Clixon 7.0, a stricter check of duplicate entries in incoming NETCONF messages was made. In Clixon 7.0, a stricter check of duplicate entries in incoming NETCONF messages was made.
More specifically: lists and leaf-lists with non-unique entries. More specifically: lists and leaf-lists with non-unique entries.
Enable to disable this check, and to allow duplicates in incoming NETCONF messages. Enable to disable this check, and to REMOVE duplicates in incoming NETCONF messages.
When duplicates are removed, only the latest entry is kept.
Note that this is an error by such a client, but there is some legacy code that uses this"; Note that this is an error by such a client, but there is some legacy code that uses this";
} }
/* HTTP and Restconf */ /* HTTP and Restconf */