Fixed: [YANG when condition evaluated as false combined with a mandatory leaf does not work](https://github.com/clicon/clixon/issues/380)

Replaced yang_mandatory() with yang_xml_mandatory() by extending existing it with when check
This commit is contained in:
Olof hagsand 2022-10-17 13:16:08 +02:00
parent 83663d4d15
commit 1eb78a78f8
7 changed files with 424 additions and 88 deletions

View file

@ -613,16 +613,20 @@ check_list_key(cxobj *xt,
* @retval -1 Error
*/
static int
choice_mandatory_check(yang_stmt *ycase,
choice_mandatory_check(cxobj *xt,
yang_stmt *ycase,
cxobj **xret)
{
int retval = -1;
yang_stmt *yc = NULL;
cbuf *cb = NULL;
int fail = 0;
int ret;
while ((yc = yn_each(ycase, yc)) != NULL) {
if (yang_mandatory(yc)){
if ((ret = yang_xml_mandatory(xt, yc)) < 0)
goto done;
if (ret == 1){
if (yang_flag_get(yc, YANG_FLAG_MARK))
yang_flag_reset(yc, YANG_FLAG_MARK);
else if (fail == 0){
@ -728,16 +732,20 @@ check_mandatory_case(cxobj *xt,
if ((y = xml_spec(x)) != NULL &&
yang_ancestor_child(y, yc, &ym, &ycnew) != 0 &&
yang_keyword_get(ycnew) == Y_CASE){
if (ym && yang_mandatory(ym)){
if (ym){
if ((ret = yang_xml_mandatory(xt, ym)) < 0)
goto done;
if (ret == 1){
if (yang_flag_get(ym, YANG_FLAG_MARK) != 0){
clicon_debug(1, "%s Already marked, shouldnt happen", __FUNCTION__);
}
yang_flag_set(ym, YANG_FLAG_MARK);
}
}
if (ycase != NULL){
if (ycnew != ycase){ /* End of case, new case */
/* Check and clear marked mandatory */
if ((ret = choice_mandatory_check(ycase, xret)) < 0)
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -749,7 +757,7 @@ check_mandatory_case(cxobj *xt,
}
else if (ycase != NULL){ /* End of case */
/* Check and clear marked mandatory */
if ((ret = choice_mandatory_check(ycase, xret)) < 0)
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -757,7 +765,7 @@ check_mandatory_case(cxobj *xt,
}
}
if (ycase){
if ((ret = choice_mandatory_check(ycase, xret)) < 0)
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -806,7 +814,7 @@ check_mandatory(cxobj *xt,
while ((yc = yn_each(yt, yc)) != NULL) {
/* Choice is more complex because of choice/case structure and possibly hierarchical */
if (yang_keyword_get(yc) == Y_CHOICE){
if (yang_mandatory(yc)){
if (yang_xml_mandatory(xt, yc)){
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL &&
@ -829,7 +837,9 @@ check_mandatory(cxobj *xt,
if (ret == 0)
goto fail;
}
if (!yang_mandatory(yc)) /* Rest of yangs are immediate children */
if ((ret = yang_xml_mandatory(xt, yc)) < 0) /* Rest of yangs are immediate children */
goto done;
if (ret == 0)
continue;
switch (yang_keyword_get(yc)){
case Y_CONTAINER:
@ -1130,6 +1140,22 @@ xml_yang_validate_all(clicon_handle h,
goto fail;
}
if (yang_config(yt) != 0){
if (yang_check_when_xpath(xt, xml_parent(xt), yt, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)",
xml_name(xt),
yang_argument_get(ys_module(yt)),
xpath);
if (xret && netconf_operation_failed_xml(xret, "application",
cbuf_get(cb)) < 0)
goto done;
goto fail;
}
if ((ret = check_mandatory(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
@ -1204,22 +1230,6 @@ xml_yang_validate_all(clicon_handle h,
nsc = NULL;
}
}
if (yang_check_when_xpath(xt, xml_parent(xt), yt, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)",
xml_name(xt),
yang_argument_get(ys_module(yt)),
xpath);
if (xret && netconf_operation_failed_xml(xret, "application",
cbuf_get(cb)) < 0)
goto done;
goto fail;
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {

View file

@ -2089,10 +2089,14 @@ xml_copy_marked(cxobj *x0,
/*! Check when condition
*
* @param[in] h Clixon handle
* @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp
* @param[in] xp XML parent
* @param[in] ys Yang node
* @param[in] xn XML node, can be NULL, in which case it is added as dummy under xp
* @param[in] xp XML parent
* @param[in] yn Yang node
* @param[out] hit when statement found
* @param[out] nrp 1: when stmt evaluates to true
* @param[out] xpathp when stmts xpath
* @retval 0 OK
* @retval -1 Error
* First variants of WHEN: Augmented and uses when using special info in node
* Second variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one.
*/
@ -2155,6 +2159,77 @@ yang_check_when_xpath(cxobj *xn,
return retval;
}
/*! Check if node is (recursively) mandatory also checking when conditional
*
* @param[in] xn Optional XML node
* @param[in] xp XML parent
* @param[in] ys YANG node
* @retval 1 Recursively contains a mandatory node
* @retval 0 Does not contain a mandatory node
* @retval -1 Error
* @see RFC7950 Sec 3:
* o mandatory node: A mandatory node is one of:
* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true".
* 2) # see below
* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child.
*/
int
yang_xml_mandatory(cxobj *xt,
yang_stmt *ys)
{
int retval = -1;
yang_stmt *ym;
cg_var *cv;
enum rfc_6020 keyw;
cxobj *xs = NULL;
int ret;
yang_stmt *yc;
int hit;
int nr;
/* Create dummy xs if not exist */
if ((xs = xml_new(yang_argument_get(ys), xt, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xs, ys);
if (yang_check_when_xpath(xs, xt, ys, &hit, &nr, NULL) < 0)
goto done;
if (hit && !nr){
retval = 0;
goto done;
}
keyw = yang_keyword_get(ys);
if (keyw == Y_LEAF || keyw == Y_CHOICE || keyw == Y_ANYDATA || keyw == Y_ANYXML){
if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){
if ((cv = yang_cv_get(ym)) != NULL){ /* shouldnt happen */
retval = cv_bool_get(cv);
goto done;
}
}
}
/* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child. */
else if (keyw == Y_CONTAINER &&
yang_find(ys, Y_PRESENCE, NULL) == NULL){
yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL) {
if ((ret = yang_xml_mandatory(xs, yc)) < 0)
goto done;
if (ret == 1)
goto mandatory;
}
}
retval = 0; /* Does not contain mandatory node */
done:
if (xs != NULL)
xml_purge(xs);
return retval;
mandatory:
retval = 1;
goto done;
}
/*! Is XML node (ie under <rpc>) an action, ie name action and belong to YANG_XML_NAMESPACE?
* @param[in] xn XML node
* @retval 1 Yes, an action

View file

@ -3213,65 +3213,6 @@ yang_desc_schema_nodeid(yang_stmt *yn,
return retval;
}
/*! Check if this leaf is mandatory or not
* Note: one can cache this value in ys_cvec instead of functionally evaluating it.
* @retval 1 yang statement is leaf and it has a mandatory sub-stmt with value true
* @retval 0 The negation of conditions for return value 1.
* @see RFC7950 Sec 3:
* o mandatory node: A mandatory node is one of:
* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true".
* 2) # see below
* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child.
*
* @note There is also this statement
* 2) A list or leaf-list node with a "min-elements" statement with a
* value greater than zero.
* which we ignore here since:
* (a) it does not consider the XML siblings and therefore returns false positives
* (b) where the actual check is catched by check_list_unique_minmax()
*/
int
yang_mandatory(yang_stmt *ys)
{
yang_stmt *ym;
cg_var *cv;
/* 1) A leaf, choice, anydata, or anyxml node with a "mandatory"
* statement with the value "true". */
if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE ||
ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){
if ((ym = yang_find(ys, Y_MANDATORY, NULL)) != NULL){
if ((cv = yang_cv_get(ym)) != NULL) /* shouldnt happen */
return cv_bool_get(cv);
}
}
#if 0 /* See note above */
/* 2) A list or leaf-list node with a "min-elements" statement with a
* value greater than zero. */
else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){
if ((ym = yang_find(ys, Y_MIN_ELEMENTS, NULL)) != NULL){
cv = yang_cv_get(ym);
return cv_uint32_get(cv) > 0; /* Not true if XML considered */
}
}
#endif
/* 3) A container node without a "presence" statement and that has at
* least one mandatory node as a child. */
else if (ys->ys_keyword == Y_CONTAINER &&
yang_find(ys, Y_PRESENCE, NULL) == NULL){
yang_stmt *yc;
int i;
for (i=0; i<ys->ys_len; i++){
yc = ys->ys_stmt[i];
if (yang_mandatory(yc))
return 1;
}
}
return 0;
}
/*! Return config state of this node
* @param[in] ys Yang statement
* @retval 0 If node has a config sub-statement and it is false