diff --git a/CHANGELOG.md b/CHANGELOG.md index 6158dbd3..447b1563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Expected: September 2022 ### Corrected Bugs +* Fixed: [YANG ordering fails for nested choice and action](YANG ordering fails for nested choice and action) * Fixed: [YANG min-elements within non-presence container does not work](https://github.com/clicon/clixon/issues/355) * Fixed: [Issues with ietf-snmp modules](https://github.com/clicon/clixon/issues/353) * Fixed: [Missing/no namespace error in YANG augments with default values](https://github.com/clicon/clixon/issues/354) diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 3082b835..065f9696 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -182,19 +182,18 @@ xml_cv_cache_clear(cxobj *xt) * Example: x1: 1 and x2: 12 are considered equal. * This is useful in searching for indexes in trees, where x1 is a search index and not a * complete tree. - * - * @note empty value/NULL is smallest value - * @note some error cases return as -1 (qsort cant handle errors) - * @note some error cases return as -1 (qsort cant handle errors) - * - * @note "comparing" x1 and x2 here judges equality from a structural (model) + * + * "Comparing" x1 and x2 here determines equality from a structural (model) * perspective, ie both have the same yang spec, if they are lists, they have the * the same keys. NOT that the values are equal! - * In other words, if x is a leaf with the same yang spec, 1 and 2 are - * "equal". + * In other words, if x is a leaf with the same yang spec, 1 and 2 are "equal". * If x is a list element (with key "k"), - * 42foo and 42bar are equal, + * 42foo and 42bar are equal, * but is not equal to 71bar + * + * @note empty value/NULL is smallest value + * @note some error cases return as -1 (qsort cant handle errors) (which?) + * - yang_order() is one */ int xml_cmp(cxobj *x1, @@ -256,6 +255,7 @@ xml_cmp(cxobj *x1, goto done; } if (y1 != y2){ + /* XXX handle errors */ yi1 = yang_order(y1); yi2 = yang_order(y2); if ((equal = yi1-yi2) != 0) @@ -388,7 +388,7 @@ xml_cmp(cxobj *x1, } /*! - * @note args are pointer ot pointers, to fit into qsort cmp function + * @note args are pointer to pointers, to fit into qsort cmp function */ static int xml_cmp_qsort(const void* arg1, @@ -476,11 +476,14 @@ xml_find_keys_notsorted(cxobj *xp, int i; cxobj *xc; yang_stmt *yc; + int yi; for (i=mid+1; i=0; i--){ /* Then decrement */ xc = xml_child_i(xp, i); yc = xml_spec(xc); - if (yangi != yang_order(yc)) /* wrong yang */ + if ((yi = yang_order(yc)) < -1) + goto done; + if (yangi != yi) /* wrong yang */ break; if (xml_cmp(xc, x1, 0, skip1, NULL) == 0){ if (clixon_xvec_append(xvec, xc) < 0) @@ -528,11 +533,14 @@ search_multi_equals(cxobj **childvec, int i; cxobj *xc; yang_stmt *yc; + int yi; for (i=mid-1; i>=0; i--){ /* First decrement */ xc = childvec[i]; yc = xml_spec(xc); - if (yangi != yang_order(yc)) /* wrong yang */ + if ((yi = yang_order(yc)) < -1) + goto done; + if (yangi != yi) /* wrong yang */ break; if (xml_cmp(x1, xc, 0, skip1, NULL) != 0) break; @@ -542,7 +550,9 @@ search_multi_equals(cxobj **childvec, for (i=mid+1; i=0; i--){ /* First decrement */ xc = clixon_xvec_i(childvec, i); yc = xml_spec(xc); - if (yangi != yang_order(yc)) /* wrong yang */ + if ((yi = yang_order(yc)) < -1) + goto done; + if (yangi != yi) /* wrong yang */ break; if (xml_cmp(x1, xc, 0, skip1, NULL) != 0) break; @@ -583,7 +596,9 @@ search_multi_equals_xvec(clixon_xvec *childvec, for (i=mid+1; i upper){ /* beyond range */ clicon_err(OE_XML, 0, "low>upper %d %d", low, upper); @@ -994,7 +1013,9 @@ xml_insert2(cxobj *xp, cmp = xml_cmp(xn, xc, 0, 0, NULL); } else{ /* Not equal yang - compute diff */ - cmp = yni - yang_order(yc); + if ((yi = yang_order(yc)) < -1) + goto done; + cmp = yni - yi; /* One case is a choice where * xc = , xn = * same order but different yang spec @@ -1072,7 +1093,8 @@ xml_insert(cxobj *xp, #endif if (yang_keyword_get(y) == Y_LIST || yang_keyword_get(y) == Y_LEAF_LIST) userorder = (yang_find(y, Y_ORDERED_BY, "user") != NULL); - yi = yang_order(y); + if ((yi = yang_order(y)) < -1) + goto done; if ((i = xml_insert2(xp, xi, y, yi, userorder, ins, key_val, nsc_key, low, upper)) < 0) diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 20033bd7..5e47ed26 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1466,40 +1466,46 @@ yang_choice(yang_stmt *y) * In (2) we increment with only 1. */ static int -order1_choice(yang_stmt *yp, - yang_stmt *y, - int *index) +yang_order1_choice(yang_stmt *yp, + yang_stmt *y, + int *index) { yang_stmt *ys; yang_stmt *yc; int i; int j; - int shortcut=0; int max=0; + int index0; + index0 = *index; for (i=0; iys_len; i++){ /* Loop through choice */ ys = yp->ys_stmt[i]; if (ys->ys_keyword == Y_CASE){ /* Loop through case */ + *index = index0; for (j=0; jys_len; j++){ yc = ys->ys_stmt[j]; - if (yang_datanode(yc) && yc == y){ - *index += j; - return 1; + if (yc->ys_keyword == Y_CHOICE){ + if (yang_order1_choice(yc, y, index) == 1) /* If one of the choices is "y" */ + return 1; + } + else { + if (yang_datanode(yc) && yc == y){ + // *index = index0 + j; + return 1; + } + (*index)++; } } - if (j>max) - max = j; + if (*index-index0 > max) + max = *index-index0; } else { - shortcut = 1; /* Shortcut, no case */ + max = 1; /* Shortcut, no case */ if (yang_datanode(ys) && ys == y) return 1; } } - if (shortcut) - (*index)++; - else - *index += max; + *index += max; return 0; } @@ -1507,44 +1513,52 @@ order1_choice(yang_stmt *yp, * @param[in] yp Parent * @param[in] y Yang datanode to find * @param[out] index Index of y in yp:s list of children - * @retval 0 not found (must be datanode) - * @retval 1 found + * @retval 0 OK: found + * @retval -1 Error: not found (must be datanode) */ static int -order1(yang_stmt *yp, - yang_stmt *y, - int *index) +yang_order1(yang_stmt *yp, + yang_stmt *y, + int *index) { + int retval = -1; yang_stmt *ys; int i; for (i=0; iys_len; i++){ ys = yp->ys_stmt[i]; if (ys->ys_keyword == Y_CHOICE){ - if (order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */ - return 1; + if (yang_order1_choice(ys, y, index) == 1) /* If one of the choices is "y" */ + break; } else { - if (!yang_datanode(ys)) + if (!yang_datanode(ys) && + yang_keyword_get(ys) != Y_ACTION) /* action is special case */ continue; if (ys == y) - return 1; + break; (*index)++; } } - return 0; + if (i < yp->ys_len) /* break -> found */ + retval = 0; + else + assert(0); // XXX + return retval; } /*! Return order of yang statement y in parents child vector * @param[in] y Find position of this data-node * @param[out] index Index of y in yp:s list of children * @retval >=0 Order of child with specified argument - * @retval -1 Not found + * @retval -1 No spec, y is NULL, which applies to eg attributes and are placed first + * @retval -2 Error: Not found * @note special handling if y is child of (sub)module */ int yang_order(yang_stmt *y) { + int retval = -2; yang_stmt *yp; yang_stmt *ypp; yang_stmt *ym; @@ -1552,9 +1566,10 @@ yang_order(yang_stmt *y) int j=0; int tot = 0; - if (y == NULL) - return -1; - + if (y == NULL){ + retval = -1; + goto done; + } /* Some special handling if yp is choice (or case) * if so, the real parent (from an xml point of view) is the parents * parent. @@ -1578,9 +1593,13 @@ yang_order(yang_stmt *y) tot += ym->ys_len; } } - if (order1(yp, y, &j) == 1) - return tot + j; - return -1; + if (yang_order1(yp, y, &j) < 0){ + clicon_err(OE_YANG, 0, "YANG node %s not ordered: not found", yang_argument_get(y)); + goto done; + } + retval = tot + j; + done: + return retval; } char * diff --git a/test/test_augment_default.sh b/test/test_augment_default.sh index 0a62f02e..b1b33bb7 100755 --- a/test/test_augment_default.sh +++ b/test/test_augment_default.sh @@ -89,21 +89,18 @@ new "wait backend" wait_backend new "cli show config startup" -#expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '' '' 'true' expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '
' '' 'true' new "cli delete map name" expectpart "$($clixon_cli -1 -f $cfg -l o delete table map name me)" 0 "" new "cli show config deleted" -#expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '
' '' 'true' expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '
' '' 'true' new "cli set map name" expectpart "$($clixon_cli -1 -f $cfg -l o set table map name x)" 0 "" new "cli show config set" -#expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '
' '' 'true' expectpart "$($clixon_cli -1 -f $cfg -l o show config xml)" 0 '
' '' 'true' if [ $BE -ne 0 ]; then diff --git a/test/test_choice.sh b/test/test_choice.sh index 425a1710..d0f30666 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -158,6 +158,27 @@ module system{ } } } + list mylist { + key k; + leaf k{ + type string; + } + choice c { + case c1 { + leaf c1 { + mandatory true; + type string; + } + choice c1c { + case c1c1 { + leaf c1c1l { + type string; + } + } + } + } + } + } } EOF @@ -181,6 +202,7 @@ discard("Discard edits (rollback 0)"), discard_changes(); show("Show a particular state of the system"){ configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set "); + xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false, NULL); } } EOF @@ -438,6 +460,21 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "netconf discard-changes" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" +new "set case mandatory + non" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "ab" "" "" + +new "netconf validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "netconf discard-changes" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + +new "set list+mandatory case" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "0xy" "" "" + +new "netconf validate ok" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf