Optimized duplicate detection and removal
This commit is contained in:
parent
ead9e8d666
commit
06e1a48480
7 changed files with 364 additions and 125 deletions
|
|
@ -17,7 +17,9 @@ Expected: January 2025
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* C-API: New no-copy `xmldb_get_cache` function for performance
|
* Performance optimization
|
||||||
|
* New no-copy `xmldb_get_cache` function for performance
|
||||||
|
* Optimized duplicate detection
|
||||||
* New: CLI generic pipe callbacks
|
* New: CLI generic pipe callbacks
|
||||||
* Add scripts in `CLICON_CLI_PIPE_DIR`
|
* Add scripts in `CLICON_CLI_PIPE_DIR`
|
||||||
* New: [feature request: support xpath functions for strings](https://github.com/clicon/clixon/issues/556)
|
* New: [feature request: support xpath functions for strings](https://github.com/clicon/clixon/issues/556)
|
||||||
|
|
|
||||||
|
|
@ -667,13 +667,20 @@ from_client_edit_config(clixon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
/* Disable duplicate check in NETCONF messages. */
|
/* Disable duplicate check in NETCONF messages. */
|
||||||
if (clicon_option_bool(h, "CLICON_NETCONF_DUPLICATE_ALLOW")){
|
if (clicon_option_bool(h, "CLICON_NETCONF_DUPLICATE_ALLOW")){
|
||||||
if ((ret = xml_duplicate_remove_recurse(xc, &xret)) < 0)
|
if (xml_duplicate_remove_recurse(xc) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if ((ret = xml_yang_validate_unique_recurse(xc, &xret)) < 0)
|
else {
|
||||||
goto done;
|
if ((ret = xml_yang_validate_unique_recurse(xc, &xret)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0){
|
||||||
|
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* 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 = xml_yang_validate_list_key_only(xc, &xret)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0){
|
if (ret == 0){
|
||||||
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
|
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +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);
|
int xml_duplicate_remove_recurse(cxobj *xt);
|
||||||
|
|
||||||
#endif /* _CLIXON_VALIDATE_MINMAX_H_ */
|
#endif /* _CLIXON_VALIDATE_MINMAX_H_ */
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,7 @@ cxobj *xml_child_i(cxobj *xn, int i);
|
||||||
cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type);
|
cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type);
|
||||||
cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
|
cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
|
||||||
int xml_child_order(cxobj *xn, cxobj *xc);
|
int xml_child_order(cxobj *xn, cxobj *xc);
|
||||||
|
int xml_vector_decrement(cxobj *x, int nr);
|
||||||
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type);
|
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type);
|
||||||
cxobj *xml_child_each_attr(cxobj *xparent, cxobj *xprev);
|
cxobj *xml_child_each_attr(cxobj *xparent, cxobj *xprev);
|
||||||
int xml_child_insert_pos(cxobj *x, cxobj *xc, int pos);
|
int xml_child_insert_pos(cxobj *x, cxobj *xc, int pos);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
@ -79,6 +80,19 @@
|
||||||
#include "clixon_xml_bind.h"
|
#include "clixon_xml_bind.h"
|
||||||
#include "clixon_validate_minmax.h"
|
#include "clixon_validate_minmax.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local types
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! List used to order values, object and strings for leaf-lists, cvk:s for lists
|
||||||
|
* consider union
|
||||||
|
*/
|
||||||
|
struct vec_order {
|
||||||
|
cxobj *vo_xml;
|
||||||
|
char **vo_strvec;
|
||||||
|
size_t vo_slen; /* length of vo_strvec (is actually global to vector) */
|
||||||
|
};
|
||||||
|
|
||||||
/*! New element last in list, check if already exists if so 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)
|
||||||
|
|
@ -112,7 +126,7 @@ unique_search_xpath(cxobj *x,
|
||||||
xi = xvec[i];
|
xi = xvec[i];
|
||||||
if ((bi = xml_body(xi)) == NULL)
|
if ((bi = xml_body(xi)) == NULL)
|
||||||
break;
|
break;
|
||||||
/* Check if bi is duplicate?
|
/* Check if bi is duplicate?
|
||||||
* XXX: sort svec?
|
* XXX: sort svec?
|
||||||
*/
|
*/
|
||||||
for (s=0; s<(*slen); s++){
|
for (s=0; s<(*slen); s++){
|
||||||
|
|
@ -137,7 +151,7 @@ unique_search_xpath(cxobj *x,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! 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]
|
||||||
|
|
@ -253,18 +267,19 @@ 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 */
|
/* Vector of key values, k00,k01,..,k0n,k10,k11,..
|
||||||
|
* Ie, if nr of keys is n, and nr of children is m, then length is n*m
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
/* Vector of xml objects (children), not really necessary, is a direct copy of x_childvec */
|
||||||
if ((xvec = calloc(xml_child_nr(xt), sizeof(cxobj*))) == NULL){
|
if ((xvec = calloc(xml_child_nr(xt), sizeof(cxobj*))) == NULL){
|
||||||
clixon_err(OE_UNIX, errno, "calloc");
|
clixon_err(OE_UNIX, errno, "calloc");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* A vector is built with key-values, for each iteration check "backward" in the vector
|
/* Loop over children, then over each key, then search "backwards" */
|
||||||
* for duplicates
|
|
||||||
*/
|
|
||||||
i = 0; /* x element index */
|
i = 0; /* x element index */
|
||||||
do {
|
do {
|
||||||
xvec[i] = x;
|
xvec[i] = x;
|
||||||
|
|
@ -465,7 +480,7 @@ check_minmax(cxobj *xp,
|
||||||
* @retval 1 Validation OK
|
* @retval 1 Validation OK
|
||||||
* @retval 0 Validation failed (xret set)
|
* @retval 0 Validation failed (xret set)
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @note recurse for non-presence container
|
* @note recurse for non-presence container
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
check_empty_list_minmax(cxobj *xt,
|
check_empty_list_minmax(cxobj *xt,
|
||||||
|
|
@ -542,9 +557,9 @@ xml_yang_minmax_new_list(cxobj *x,
|
||||||
if (yang_keyword_get(yu) != Y_UNIQUE)
|
if (yang_keyword_get(yu) != Y_UNIQUE)
|
||||||
continue;
|
continue;
|
||||||
/* Here is a list w unique constraints identified by:
|
/* Here is a list w unique constraints identified by:
|
||||||
* its first element x, its yang spec y, its parent xt, and
|
* its first element x, its yang spec y, its parent xt, and
|
||||||
* a unique yang spec yu,
|
* a unique yang spec yu,
|
||||||
* Two cases:
|
* Two cases:
|
||||||
* 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"
|
||||||
*/
|
*/
|
||||||
|
|
@ -625,7 +640,7 @@ xml_yang_minmax_new_leaf_list(cxobj *x0,
|
||||||
/*! Perform gap analysis in a child-vector interval [ye,y]
|
/*! Perform gap analysis in a child-vector interval [ye,y]
|
||||||
*
|
*
|
||||||
* Gap analysis here meaning if there is a list x with min-element constraint but there are no
|
* Gap analysis here meaning if there is a list x with min-element constraint but there are no
|
||||||
* x elements in an interval of the children of xt.
|
* x elements in an interval of the children of xt.
|
||||||
* For example, assume the yang of xt is yt and is defined as:
|
* For example, assume the yang of xt is yt and is defined as:
|
||||||
* yt {
|
* yt {
|
||||||
* list a;
|
* list a;
|
||||||
|
|
@ -634,8 +649,8 @@ xml_yang_minmax_new_leaf_list(cxobj *x0,
|
||||||
* }
|
* }
|
||||||
* Further assume that xt is:
|
* Further assume that xt is:
|
||||||
* <xt><a><a><b><b></xt>
|
* <xt><a><a><b><b></xt>
|
||||||
* Then a call to this function could be ye=a, y=b.
|
* Then a call to this function could be ye=a, y=b.
|
||||||
* By iterating over the children of yt in the interval [a,b] it will find "x" with a min-element
|
* By iterating over the children of yt in the interval [a,b] it will find "x" with a min-element
|
||||||
* constraint.
|
* constraint.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
|
|
@ -702,13 +717,13 @@ xml_yang_minmax_gap_analysis(cxobj *xt,
|
||||||
* x -> bc
|
* x -> bc
|
||||||
* x -> ab
|
* x -> ab
|
||||||
*
|
*
|
||||||
* Min-max constraints:
|
* Min-max constraints:
|
||||||
* Find upper and lower bound of existing lists and report violations
|
* Find upper and lower bound of existing lists and report violations
|
||||||
* Somewhat tricky to find violation of min-elements of empty
|
* Somewhat tricky to find violation of min-elements of empty
|
||||||
* lists, but this is done by a "gap-detection" mechanism, which detects
|
* lists, but this is done by a "gap-detection" mechanism, which detects
|
||||||
* gaps in the xml nodes given the ancestor Yang structure.
|
* gaps in the xml nodes given the ancestor Yang structure.
|
||||||
* But no gap analysis is done if the yang spec of the top-level xml is unknown
|
* But no gap analysis is done if the yang spec of the top-level xml is unknown
|
||||||
* Example:
|
* Example:
|
||||||
* Yang structure: y1, y2, y3,
|
* Yang structure: y1, y2, y3,
|
||||||
* XML structure: [x1, x1], [x3, x3] where [x2] list is missing
|
* XML structure: [x1, x1], [x3, x3] where [x2] list is missing
|
||||||
* @note min-element constraints on empty lists are not detected on top-level.
|
* @note min-element constraints on empty lists are not detected on top-level.
|
||||||
|
|
@ -716,8 +731,8 @@ xml_yang_minmax_gap_analysis(cxobj *xt,
|
||||||
* XML node. This may not be a large problem since it would mean empty configs
|
* XML node. This may not be a large problem since it would mean empty configs
|
||||||
* are not allowed.
|
* are not allowed.
|
||||||
* RFC 7950 7.7.5: regarding min-max elements check
|
* RFC 7950 7.7.5: regarding min-max elements check
|
||||||
* The behavior of the constraint depends on the type of the
|
* The behavior of the constraint depends on the type of the
|
||||||
* leaf-list's or list's closest ancestor node in the schema tree
|
* leaf-list's or list's closest ancestor node in the schema tree
|
||||||
* that is not a non-presence container (see Section 7.5.1):
|
* that is not a non-presence container (see Section 7.5.1):
|
||||||
* o If no such ancestor exists in the schema tree, the constraint
|
* o If no such ancestor exists in the schema tree, the constraint
|
||||||
* is enforced.
|
* is enforced.
|
||||||
|
|
@ -733,9 +748,9 @@ xml_yang_minmax_gap_analysis(cxobj *xt,
|
||||||
* 3 null list neq gap analysis, new: list key check
|
* 3 null list neq gap analysis, new: list key check
|
||||||
* 4 nolist list neq gap analysis, new: list key check
|
* 4 nolist list neq gap analysis, new: list key check
|
||||||
* 5 nolist nolist eq error
|
* 5 nolist nolist eq error
|
||||||
* 6 nolist nolist neq gap analysis; nopresence-check;
|
* 6 nolist nolist neq gap analysis; nopresence-check;
|
||||||
* 7 null nolist neq gap analysis; nopresence-check;
|
* 7 null nolist neq gap analysis; nopresence-check;
|
||||||
* 8 list nolist neq gap analysis; yprev: check-minmax; nopresence-check;
|
* 8 list nolist neq gap analysis; yprev: check-minmax; nopresence-check;
|
||||||
* @param[in] xt XML parent (may have lists w unique constraints as child)
|
* @param[in] xt XML parent (may have lists w unique constraints as child)
|
||||||
* @param[in] presence Set if called in a recursive loop (the caller will recurse anyway),
|
* @param[in] presence Set if called in a recursive loop (the caller will recurse anyway),
|
||||||
* otherwise non-presence containers will be traversed
|
* otherwise non-presence containers will be traversed
|
||||||
|
|
@ -1035,118 +1050,253 @@ xml_yang_validate_unique_recurse(cxobj *xt,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! YANG unique check and remove duplicates, keep last
|
/*----------- New linear vector code -----------------*/
|
||||||
*
|
|
||||||
* Assume xt:s children are sorted and yang populated.
|
static int
|
||||||
* @param[in] xt XML parent (may have lists w unique constraints as child)
|
vec_free(struct vec_order *vec,
|
||||||
* @param[out] xret Error XML tree. Free with xml_free after use
|
size_t vlen)
|
||||||
* @retval 2 Locally abort this subtree, continue with others
|
{
|
||||||
* @retval 1 Abort, dont continue with others, return 1 to end user
|
int v;
|
||||||
* @retval 0 OK, continue
|
|
||||||
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
for (v=0; v<vlen; v++){
|
||||||
* @see xml_yang_validate_minmax which include these unique tests
|
if (vec[v].vo_strvec)
|
||||||
|
free(vec[v].vo_strvec);
|
||||||
|
}
|
||||||
|
free(vec);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Leaf-list qsort comparison function
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
xml_duplicate_remove(cxobj *xt,
|
cmp_list_qsort(const void *arg1,
|
||||||
void *arg)
|
const void *arg2)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
struct vec_order *v1 = (struct vec_order *)arg1;
|
||||||
cxobj **xret = (cxobj **)arg;
|
struct vec_order *v2 = (struct vec_order *)arg2;
|
||||||
cxobj *x;
|
int i1;
|
||||||
yang_stmt *y;
|
int i2;
|
||||||
yang_stmt *yprev;
|
int eq;
|
||||||
enum rfc_6020 keyw;
|
int i;
|
||||||
int again;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
again = 1;
|
eq = 0;
|
||||||
while (again){
|
for (i=0; i<v1->vo_slen; i++){
|
||||||
again = 0;
|
assert(v1->vo_strvec[i]);
|
||||||
yprev = NULL;
|
assert(v2->vo_strvec[i]);
|
||||||
x = NULL;
|
if ((eq = strcmp(v1->vo_strvec[i], v2->vo_strvec[i])) != 0)
|
||||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){
|
break;
|
||||||
if ((y = xml_spec(x)) == NULL)
|
}
|
||||||
continue;
|
if (eq != 0)
|
||||||
keyw = yang_keyword_get(y);
|
return eq;
|
||||||
if (keyw == Y_LIST || keyw == Y_LEAF_LIST){
|
i1 = xml_enumerate_get(v1->vo_xml);
|
||||||
if (y == yprev){ /* equal: continue, assume list check does look-forward */
|
i2 = xml_enumerate_get(v2->vo_xml);
|
||||||
continue;
|
if (i1 > i2)
|
||||||
}
|
return 1;
|
||||||
/* new list check */
|
else if (i1 < i2)
|
||||||
switch (keyw){
|
return -1;
|
||||||
case Y_LIST:
|
else
|
||||||
if ((ret = xml_yang_minmax_new_list(x, xt, y, XML_FLAG_DEL, xret)) < 0)
|
return 0;
|
||||||
goto done;
|
}
|
||||||
if (ret == 0){
|
|
||||||
if (xml_tree_prune_flags1(xt, XML_FLAG_DEL, XML_FLAG_DEL, 0, &again) < 0)
|
/*! Remove duplicates list
|
||||||
goto done;
|
*/
|
||||||
if (again){
|
static int
|
||||||
if (xret && *xret){
|
remove_duplicates_list(struct vec_order *vec,
|
||||||
xml_free(*xret);
|
size_t vlen,
|
||||||
*xret = NULL;
|
int *nr)
|
||||||
}
|
{
|
||||||
break;
|
int retval = -1;
|
||||||
}
|
int v;
|
||||||
goto fail;
|
int i;
|
||||||
}
|
|
||||||
break;
|
if (nr)
|
||||||
case Y_LEAF_LIST:
|
*nr = 0;
|
||||||
if ((ret = xml_yang_minmax_new_leaf_list(x, xt, y, XML_FLAG_DEL, xret)) < 0)
|
for (v=1; v<vlen; v++){
|
||||||
goto done;
|
for (i=0; i<vec[v-1].vo_slen; i++){
|
||||||
if (ret == 0){
|
if (clicon_strcmp(vec[v-1].vo_strvec[i], vec[v].vo_strvec[i]) != 0)
|
||||||
if (xml_tree_prune_flags1(xt, XML_FLAG_DEL, XML_FLAG_DEL, 0, &again) < 0)
|
break;
|
||||||
goto done;
|
}
|
||||||
if (again){
|
if (i==vec[v-1].vo_slen){
|
||||||
if (xret && *xret){
|
if (xml_purge(vec[v-1].vo_xml) < 0)
|
||||||
xml_free(*xret);
|
goto done;
|
||||||
*xret = NULL;
|
if (nr)
|
||||||
}
|
(*nr)++;
|
||||||
break;
|
|
||||||
}
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (again)
|
|
||||||
break;
|
|
||||||
yprev = y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
fail:
|
}
|
||||||
retval = 1;
|
|
||||||
goto done;
|
static int
|
||||||
|
vec_order_analyze(yang_stmt *y,
|
||||||
|
struct vec_order *vec,
|
||||||
|
size_t vlen,
|
||||||
|
cxobj *x)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int nr = 0;
|
||||||
|
|
||||||
|
if (yang_find(y, Y_ORDERED_BY, "user") != NULL)
|
||||||
|
qsort(vec, vlen, sizeof(*vec), cmp_list_qsort);
|
||||||
|
if (remove_duplicates_list(vec, vlen, &nr) < 0)
|
||||||
|
goto done;
|
||||||
|
if (x && nr)
|
||||||
|
xml_vector_decrement(x, nr);
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! 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 0 OK
|
||||||
|
* @retval -1 Error
|
||||||
|
* @see xml_yang_validate_minmax which include these unique tests
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
xml_duplicate_remove(cxobj *xt)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
cxobj *x;
|
||||||
|
yang_stmt *y;
|
||||||
|
yang_stmt *y0;
|
||||||
|
enum rfc_6020 keyw;
|
||||||
|
char *b;
|
||||||
|
size_t vlen = 0;
|
||||||
|
struct vec_order *vec = NULL;
|
||||||
|
cvec *cvk;
|
||||||
|
cg_var *cvi;
|
||||||
|
size_t clen;
|
||||||
|
size_t slen0; /* sanity check, ensure all vectors are equal length */
|
||||||
|
char *str;
|
||||||
|
cxobj *xi;
|
||||||
|
int v;
|
||||||
|
|
||||||
|
xml_enumerate_children(xt); // Could be done in-line
|
||||||
|
y0 = NULL;
|
||||||
|
slen0 = 0;
|
||||||
|
x = NULL;
|
||||||
|
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||||
|
if ((y = xml_spec(x)) == NULL)
|
||||||
|
continue;
|
||||||
|
if (y != y0 && vec != NULL){ /* New */
|
||||||
|
if (vec_order_analyze(y0, vec, vlen, x) < 0)
|
||||||
|
goto done;
|
||||||
|
if (vec_free(vec, vlen) < 0)
|
||||||
|
goto done;
|
||||||
|
vec = NULL;
|
||||||
|
vlen = 0;
|
||||||
|
slen0 = 0;
|
||||||
|
}
|
||||||
|
keyw = yang_keyword_get(y);
|
||||||
|
switch (keyw){
|
||||||
|
case Y_LIST:
|
||||||
|
if ((cvk = yang_cvec_get(y)) == NULL)
|
||||||
|
continue;
|
||||||
|
if ((clen = cvec_len(cvk)) == 0)
|
||||||
|
continue;
|
||||||
|
if (vec>0 && slen0 != clen){ /* Sanity check */
|
||||||
|
clixon_err(OE_YANG, 0, "List key vector mismatch %lu != %lu", slen0, clen);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* see check_unique_list_direct */
|
||||||
|
if ((vec = realloc(vec, (vlen+1)*sizeof(*vec))) == NULL){
|
||||||
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
vec[vlen].vo_slen = clen;
|
||||||
|
vec[vlen].vo_xml = x;
|
||||||
|
if ((vec[vlen].vo_strvec = calloc(vec[vlen].vo_slen , sizeof(char*))) == NULL){
|
||||||
|
clixon_err(OE_UNIX, errno, "calloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
cvi = NULL;
|
||||||
|
v = 0;
|
||||||
|
while ((cvi = cvec_each(cvk, cvi)) != NULL){
|
||||||
|
if ((str = cv_string_get(cvi)) == NULL)
|
||||||
|
break;
|
||||||
|
if ((xi = xml_find(x, str)) == NULL)
|
||||||
|
break;
|
||||||
|
if ((b = xml_body(xi)) == NULL)
|
||||||
|
vec[vlen].vo_strvec[v++] = "";
|
||||||
|
else
|
||||||
|
vec[vlen].vo_strvec[v++] = b;
|
||||||
|
}
|
||||||
|
if (cvi != NULL){ /* No key or null: revert and skip */
|
||||||
|
free(vec[vlen].vo_strvec);
|
||||||
|
memset(&vec[vlen], 0, sizeof(*vec));
|
||||||
|
vec[vlen].vo_strvec = NULL;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
slen0 = clen;
|
||||||
|
vlen++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Y_LEAF_LIST:
|
||||||
|
if (vec>0 && slen0 != 1){ /* Sanity check */
|
||||||
|
clixon_err(OE_YANG, 0, "Leaf-list key vector mismatch %lu != 1", slen0);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((vec = realloc(vec, (vlen+1)*sizeof(struct vec_order))) == NULL){
|
||||||
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
vec[vlen].vo_xml = x;
|
||||||
|
vec[vlen].vo_slen = 1;
|
||||||
|
if ((vec[vlen].vo_strvec = calloc(vec[vlen].vo_slen, sizeof(char*))) == NULL){
|
||||||
|
clixon_err(OE_UNIX, errno, "calloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
b = xml_body(x);
|
||||||
|
vec[vlen].vo_strvec[0] = b;
|
||||||
|
slen0 = 1;
|
||||||
|
vlen++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
y0 = y;
|
||||||
|
}
|
||||||
|
if (y0 && vec != NULL){
|
||||||
|
if (vec_order_analyze(y0, vec, vlen, NULL) < 0)
|
||||||
|
goto done;
|
||||||
|
if (vec_free(vec, vlen) < 0)
|
||||||
|
goto done;
|
||||||
|
vec = NULL;
|
||||||
|
vlen = 0;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (vec)
|
||||||
|
free(vec);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Recursive YANG unique check and remove duplicates, keep last
|
/*! Recursive YANG unique check and remove duplicates, keep last
|
||||||
*
|
*
|
||||||
* @param[in] xt XML parent (may have lists w unique constraints as child)
|
* @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 0 Validation OK
|
||||||
* @retval 1 Validation OK
|
|
||||||
* @retval 0 Validation failed (xret set)
|
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @see xml_yang_validate_unique_recurse
|
* @see xml_yang_validate_unique_recurse This function destructively removes
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
xml_duplicate_remove_recurse(cxobj *xt,
|
xml_duplicate_remove_recurse(cxobj *xt)
|
||||||
cxobj **xret)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
int ret;
|
cxobj *x;
|
||||||
|
|
||||||
if ((ret = xml_apply0(xt, CX_ELMNT, xml_duplicate_remove, xret)) < 0)
|
if (xml_duplicate_remove(xt) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 1)
|
x = NULL;
|
||||||
goto fail;
|
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||||
retval = 1;
|
if (xml_duplicate_remove_recurse(x) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
fail:
|
|
||||||
retval = 0;
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -909,6 +909,16 @@ xml_child_order(cxobj *xp,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Advanced function to decrement _x_vector_i if objects have been removed
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
xml_vector_decrement(cxobj *x,
|
||||||
|
int nr)
|
||||||
|
{
|
||||||
|
x->_x_vector_i -= nr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Iterator over xml children objects
|
/*! Iterator over xml children objects
|
||||||
*
|
*
|
||||||
* @param[in] xparent xml tree node whose children should be iterated
|
* @param[in] xparent xml tree node whose children should be iterated
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,10 @@ module unique{
|
||||||
leaf-list b{
|
leaf-list b{
|
||||||
type string;
|
type string;
|
||||||
}
|
}
|
||||||
|
leaf-list buser{
|
||||||
|
ordered-by user;
|
||||||
|
type string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
@ -98,7 +102,7 @@ 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 list with duplicate"
|
new "Add list with duplicate 2"
|
||||||
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\">
|
||||||
<server>
|
<server>
|
||||||
<name>one</name>
|
<name>one</name>
|
||||||
|
|
@ -142,9 +146,8 @@ if [ $BE -ne 0 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check CLICON_NETCONF_DUPLICATE_ALLOW
|
# Check CLICON_NETCONF_DUPLICATE_ALLOW
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "start backend -s init -f $cfg"
|
new "start backend -s init -f $cfg -o CLICON_NETCONF_DUPLICATE_ALLOW=true"
|
||||||
# start new backend
|
# start new backend
|
||||||
start_backend -s init -f $cfg -o CLICON_NETCONF_DUPLICATE_ALLOW=true
|
start_backend -s init -f $cfg -o CLICON_NETCONF_DUPLICATE_ALLOW=true
|
||||||
fi
|
fi
|
||||||
|
|
@ -246,16 +249,82 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS>
|
||||||
new "netconf discard-changes"
|
new "netconf discard-changes"
|
||||||
expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "<rpc $DEFAULTNS><discard-changes/></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
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"
|
new "leaf-list with dups"
|
||||||
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>aaa</b>
|
<b>ccc</b>
|
||||||
<b>aaa</b>
|
<b>aaa</b>
|
||||||
<b>bbb</b>
|
<b>bbb</b>
|
||||||
|
<b>aaa</b>
|
||||||
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
||||||
|
|
||||||
new "Check leaf-list no duplicates"
|
new "Check leaf-list with dups"
|
||||||
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>"
|
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><b>ccc</b></c></data></rpc-reply>"
|
||||||
|
|
||||||
|
new "leaf-list with dups ordered-by user"
|
||||||
|
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\">
|
||||||
|
<buser>CCC</buser>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
<buser>BBB</buser>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
||||||
|
|
||||||
|
new "Check"
|
||||||
|
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\"><buser>CCC</buser><buser>BBB</buser><buser>AAA</buser></c></data></rpc-reply>"
|
||||||
|
|
||||||
|
new "list/leaf-list mix"
|
||||||
|
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>ccc</b>
|
||||||
|
<buser>CCC</buser>
|
||||||
|
<user>
|
||||||
|
<name>bbb</name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b>aaa</b>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
<user>
|
||||||
|
<name>aaa</name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b>bbb</b>
|
||||||
|
<buser>BBB</buser>
|
||||||
|
<user>
|
||||||
|
<name>bbb</name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b>aaa</b>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
||||||
|
|
||||||
|
new "Check mix"
|
||||||
|
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>bbb</name><value>foo</value></user><b>aaa</b><b>bbb</b><b>ccc</b><buser>CCC</buser><buser>BBB</buser><buser>AAA</buser></c></data></rpc-reply>"
|
||||||
|
|
||||||
|
new "Mix with empty"
|
||||||
|
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>ccc</b>
|
||||||
|
<buser>CCC</buser>
|
||||||
|
<user>
|
||||||
|
<name></name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b></b>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
<user>
|
||||||
|
<name>aaa</name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b>bbb</b>
|
||||||
|
<buser>BBB</buser>
|
||||||
|
<user>
|
||||||
|
<name></name>
|
||||||
|
<value>foo</value>
|
||||||
|
</user>
|
||||||
|
<b></b>
|
||||||
|
<buser>AAA</buser>
|
||||||
|
</c></config></edit-config></rpc>" "" "<rpc-reply $DEFAULTNS><ok/></rpc-reply>"
|
||||||
|
|
||||||
|
new "Check mix w empty"
|
||||||
|
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/><value>foo</value></user><b/><b>bbb</b><b>ccc</b><buser>CCC</buser><buser>BBB</buser><buser>AAA</buser></c></data></rpc-reply>"
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "Kill backend"
|
new "Kill backend"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue