* YANG when statement in conjunction with grouping/uses/augment

* Several cases were not implemented fully according to RFC 7950
    * Do not extend default values if when statements evaluate to false
    * Do not allow edit-config of nodes if when statements evaluate to false (Sec 8.3.2)
    * If a key leaf is defined in a grouping that is used in a list, the "uses" statement MUST NOT have a "when" statement. (See 7.21.5)
  * See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/2$
This commit is contained in:
Olof hagsand 2021-05-12 08:42:15 +02:00
parent 5c7498ee40
commit 783b0a4857
20 changed files with 577 additions and 164 deletions

View file

@ -34,7 +34,13 @@ Expected: June 2021
### New features
* Yang deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211)
* YANG when statement in conjunction with grouping/uses/augment
* Several cases were not implemented fully according to RFC 7950:
* Do not extend default values if when statements evaluate to false
* Do not allow edit-config of nodes if when statements evaluate to false (Sec 8.3.2)
* If a key leaf is defined in a grouping that is used in a list, the "uses" statement MUST NOT have a "when" statement. (See 7.21.5)
* See [yang uses's substatement when has no effect #218](https://github.com/clicon/clixon/issues/2$
* YANG deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211)
* See RFC7950 Sec 5.6.3
### API changes on existing protocol/config features

View file

@ -1837,12 +1837,9 @@ restconf_sig_term(int arg)
{
static int i=0;
clicon_debug(1, "%s", __FUNCTION__);
if (i++ == 0){
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
__PROGRAM__, __FUNCTION__, getpid(), arg);
}
else
if (i++ > 0) /* Allow one sigterm before proper exit */
exit(-1);
/* This should ensure no more accepts or incoming packets are processed because next time eventloop
* is entered, it will terminate.

View file

@ -380,7 +380,7 @@ api_operations(clicon_handle h,
int pretty,
restconf_media media_out)
{
int retval;
int retval = -1;
cxobj *xerr = NULL;
clicon_debug(1, "%s", __FUNCTION__);

View file

@ -144,10 +144,10 @@ typedef enum yang_bind yang_bind;
typedef struct xml cxobj; /* struct defined in clicon_xml.c */
/*! Callback function type for xml_apply
* @retval -1 Error, aborted at first error encounter
* @retval -1 Error, aborted at first error encounter, return -1 to end user
* @retval 0 OK, continue
* @retval 1 Abort, dont continue with others
* @retval 2 Locally, just abort this subtree, continue with others
* @retval 1 Abort, dont continue with others, return 1 to end user
* @retval 2 Locally abort this subtree, continue with others
*/
typedef int (xml_applyfn_t)(cxobj *x, void *arg);

View file

@ -136,7 +136,7 @@ enum rfc_6020{
Y_UNKNOWN,
Y_USES,
Y_VALUE,
Y_WHEN,
Y_WHEN, /* See also ys_when_xpath / ys_when_nsc */
Y_YANG_VERSION,
Y_YIN_ELEMENT,
Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */

View file

@ -203,13 +203,79 @@ check_body_namespace(cxobj *x0,
return retval;
}
/*! Check yang when condition between a new xml x1 and old x0
*
* check if there is a when condition. First try it on the new request (x1), then on the
* existing (x0).
* @param[in] x0p Parent of x0
* @param[in] x1 XML tree which modifies base
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* @note There may be some combination cases (x0+x1) that are not covered in this function.
*/
static int
check_when_condition(cxobj *x0p,
cxobj *x1,
yang_stmt *y0,
cbuf *cbret)
{
int retval = -1;
char *xpath = NULL;
cvec *nsc = NULL;
int nr;
cxobj *x1p;
yang_stmt *y = NULL;
cbuf *cberr = NULL;
if ((y = y0) != NULL ||
(y = (yang_stmt*)xml_spec(x1)) != NULL){
if ((xpath = yang_when_xpath_get(y)) != NULL){
nsc = yang_when_nsc_get(y);
x1p = xml_parent(x1);
if ((nr = xpath_vec_bool(x1p, nsc, "%s", xpath)) < 0) /* Try request */
goto done;
if (nr == 0){
/* Try existing tree */
if ((nr = xpath_vec_bool(x0p, nsc, "%s", xpath)) < 0)
goto done;
if (nr == 0){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Node '%s' tagged with 'when' condition '%s' in module '%s' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)",
yang_argument_get(y),
xpath,
yang_argument_get(ys_module(y)));
if (netconf_unknown_element(cbret, "application", yang_argument_get(y),
cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
}
}
}
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
return retval;
fail:
retval = 0;
goto done;
}
/*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] h Clicon handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0
* @param[in] x0t
* @param[in] x1 XML tree which modifies base
* @param[in] x1t Request root node (nacm needs this)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] username User name of requestor for nacm
* @param[in] xnacm NACM XML tree (only if !permit)
@ -264,6 +330,10 @@ text_modify(clicon_handle h,
clicon_err(OE_XML, EINVAL, "x1 is missing");
goto done;
}
if ((ret = check_when_condition(x0p, x1, y0, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Check for operations embedded in tree according to netconf */
if ((ret = attr_ns_value(x1, "operation", NETCONF_BASE_NAMESPACE,
cbret, &opstr)) < 0)

View file

@ -1227,7 +1227,7 @@ xml_yang_validate_all(clicon_handle h,
nsc = NULL;
}
}
/* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
/* First variant of when, actual "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
if ((yc = yang_find(ys, Y_WHEN, NULL)) != NULL){
xpath = yang_argument_get(yc); /* "when" has xpath argument */
/* WHEN xpath needs namespace context */
@ -1253,7 +1253,9 @@ xml_yang_validate_all(clicon_handle h,
goto fail;
}
}
/* Augmented when using special struct. */
/* Second variants of WHEN:
* Augmented and uses when using special info in node
*/
if ((xpath = yang_when_xpath_get(ys)) != NULL){
if ((nr = xpath_vec_bool(xml_parent(xt), yang_when_nsc_get(ys),
"%s", xpath)) < 0)
@ -1263,7 +1265,7 @@ xml_yang_validate_all(clicon_handle h,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed augmented WHEN condition %s of node %s in module %s",
cprintf(cb, "Failed augmented 'when' condition '%s' of node '%s' in module '%s'",
xpath,
xml_name(xt),
yang_argument_get(ys_module(ys)));

View file

@ -1142,6 +1142,8 @@ xml_default1(yang_stmt *yt,
cxobj *xc;
int top=0; /* Top symbol (set default namespace) */
int create = 0;
char *xpath;
int nr;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
@ -1162,6 +1164,13 @@ xml_default1(yang_stmt *yt,
switch (yang_keyword_get(yc)){
case Y_LEAF:
if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
/* Check when statement from uses or augment */
if ((xpath = yang_when_xpath_get(yc)) != NULL){
if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0)
goto done;
if (nr == 0)
break; /* Do not create default if xpath fails */
}
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
@ -1172,6 +1181,13 @@ xml_default1(yang_stmt *yt,
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* Check when statement from uses or augment */
if ((xpath = yang_when_xpath_get(yc)) != NULL){
if ((nr = xpath_vec_bool(xt, yang_when_nsc_get(yc), "%s", xpath)) < 0)
goto done;
if (nr == 0)
break; /* Do not create default if xpath fails */
}
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?

View file

@ -3192,7 +3192,7 @@ yang_container_cli_hide(yang_stmt *ys,
/*! Check if yang node yn has key-stmt as child which matches name
*
* The function looks at the LIST argument string (not actual children)
* @param[in] yn Yang node with sub-statements (look for a key child)
* @param[in] yn Yang list node with sub-statements (look for a key child)
* @param[in] name Check if this name (eg "b") is a key in the yang key statement
*
* @retval -1 Error

View file

@ -90,8 +90,8 @@ struct yang_stmt{
types as <module>:<id> list
*/
yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */
char *ys_when_xpath; /* Special conditional for a "when"-associated augment xpath */
cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment namespace ctx */
char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */
cvec *ys_when_nsc; /* Special conditional for a "when"-associated augment/uses namespace ctx */
int _ys_vector_i; /* internal use: yn_each */
};

View file

@ -104,6 +104,9 @@
/* Size of json read buffer when reading from file*/
#define BUFLEN 1024
/* Forward */
static int yang_expand_grouping(yang_stmt *yn);
/*! Resolve a grouping name from a module, includes looking in submodules
*/
static yang_stmt *
@ -340,7 +343,9 @@ yang_augment_node(yang_stmt *ys)
if (yn_insert(ytarget, yc) < 0)
goto done;
/* If there is an associated when statement, add a special when struct to the yang */
/* If there is an associated when statement, add a special when struct to the yang
* see xml_yang_validate_all
*/
if (ywhen){
if (yang_when_xpath_set(yc, wxpath) < 0)
goto done;
@ -454,59 +459,73 @@ ys_do_refine(yang_stmt *yr,
return retval;
}
/*! Macro expansion of grouping/uses done in step 2 of yang parsing
* RFC7950:
* Identifiers appearing inside the grouping are resolved
* relative to the scope in which the grouping is defined, not where it is
* used. Prefix mappings, type names, grouping names, and extension usage are
* evaluated in the hierarchy where the "grouping" statement appears.
* The identifiers defined in the grouping are not bound to a namespace
* until the contents of the grouping are added to the schema tree via a
* "uses" statement that does not appear inside a "grouping" statement,
* at which point they are bound to the namespace of the current module.
/*! Yang node yg is a leaf in yang node list yn
* Could be made to a generic function used elsewhere as well
* @param[in] y Yang leaf
* @param[in] yp Yang list parent
* @retval 0 No, y is not a key leaf in list yp
* @retval 1 Yes, y is a key leaf in list yp
*/
static int
yang_expand_grouping(yang_stmt *yn)
ys_iskey(yang_stmt *y,
yang_stmt *yp)
{
cvec *cvv;
cg_var *cv;
char *name;
if (yang_keyword_get(y) != Y_LEAF)
return 0;
if (yang_keyword_get(yp) != Y_LIST)
return 0;
if ((cvv = yang_cvec_get(yp)) == NULL)
return 0;
name = yang_argument_get(y);
cv = NULL;
while ((cv = cvec_each(cvv, cv)) != NULL) {
if (strcmp(name, cv_string_get(cv)) == 0)
return 1;
}
return 0;
}
/*! Helper function to yang_expand_grouping
* @param[in] yn Yang parent node of uses ststement
* @param[in] ys Uses statement
* @retval 0 OK
* @retval -1 Error
*/
static int
yang_expand_uses_node(yang_stmt *yn,
yang_stmt *ys,
int i)
{
int retval = -1;
yang_stmt *ys = NULL;
char *id = NULL;
char *prefix = NULL;
yang_stmt *ygrouping; /* grouping original */
yang_stmt *ygrouping2; /* grouping copy */
yang_stmt *yg; /* grouping child */
yang_stmt *yr; /* refinement */
yang_stmt *yp;
int glen;
int i;
size_t size;
int j;
int k;
char *id = NULL;
char *prefix = NULL;
size_t size;
yang_stmt *yp;
yang_stmt *ywhen;
char *wxpath = NULL; /* xpath of when statement */
cvec *wnsc = NULL; /* namespace context of when statement */
/* Cannot use yang_apply here since child-list is modified (is destructive) */
i = 0;
while (i < yang_len_get(yn)){
ys = yn->ys_stmt[i];
switch (yang_keyword_get(ys)){
case Y_USES:
/* Split argument into prefix and name */
if (nodeid_split(yang_argument_get(ys), &prefix, &id) < 0)
goto done;
if (ys_grouping_resolve(ys, prefix, id, &ygrouping) < 0)
goto done;
if (prefix){
free(prefix);
prefix = NULL;
}
if (id){
free(id);
id = NULL;
}
if (ygrouping == NULL){
clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"",
__FUNCTION__, yang_argument_get(ys), yang_argument_get(ys_module(ys)));
goto done;
break;
}
/* Check so that this uses statement is not a descendant of the grouping
*/
@ -562,6 +581,12 @@ yang_expand_grouping(yang_stmt *yn)
&yn->ys_stmt[i+1],
size);
}
/* Find when statement, if present */
if ((ywhen = yang_find(ys, Y_WHEN, NULL)) != NULL){
wxpath = yang_argument_get(ywhen);
if (xml_nsctx_yang(ywhen, &wnsc) < 0)
goto done;
}
/* Iterate through refinements and modify grouping copy
* See RFC 7950 7.13.2 yrt is the refine target node
*/
@ -584,7 +609,7 @@ yang_expand_grouping(yang_stmt *yn)
/* RFC: The argument is a string that identifies a node in the
* grouping. I interpret that as only one node --> break */
break;
}
} /* while yr */
/* Then copy and insert each child element from ygrouping2 to yn */
k=0;
for (j=0; j<yang_len_get(ygrouping2); j++){
@ -594,6 +619,27 @@ yang_expand_grouping(yang_stmt *yn)
ys_free(yg);
continue;
}
/* If there is an associated when statement, add a special when struct to the yang
* see xml_yang_validate_all
*/
if (ywhen){
if (ys_iskey(yg, yn)){
/* RFC 7950 Sec 7.21.5:
* If a key leaf is defined in a grouping that is used in a list, the
* "uses" statement MUST NOT have a "when" statement.
*/
clicon_err(OE_YANG, 0, "Key leaf '%s' defined in grouping '%s' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5",
yang_argument_get(yg),
yang_argument_get(ygrouping)
);
goto done;
}
if (yang_when_xpath_set(yg, wxpath) < 0)
goto done;
if (yang_when_nsc_set(yg, wnsc) < 0)
goto done;
}
yn->ys_stmt[i+k] = yg;
yg->ys_parent = yn;
k++;
@ -603,6 +649,47 @@ yang_expand_grouping(yang_stmt *yn)
/* Remove the grouping copy */
ygrouping2->ys_len = 0; /* Cant do with get access function */
ys_free(ygrouping2);
retval = 0;
done:
if (wnsc)
cvec_free(wnsc);
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}
/*! Macro expansion of grouping/uses done in step 2 of yang parsing
* RFC7950:
* Identifiers appearing inside the grouping are resolved
* relative to the scope in which the grouping is defined, not where it is
* used. Prefix mappings, type names, grouping names, and extension usage are
* evaluated in the hierarchy where the "grouping" statement appears.
* The identifiers defined in the grouping are not bound to a namespace
* until the contents of the grouping are added to the schema tree via a
* "uses" statement that does not appear inside a "grouping" statement,
* at which point they are bound to the namespace of the current module.
* @param[in] yn Yang node for recursive iteration
* @retval 0 OK
* @retval -1 Error
*/
static int
yang_expand_grouping(yang_stmt *yn)
{
int retval = -1;
yang_stmt *ys = NULL;
int i;
/* Cannot use yang_apply here since child-list is modified (is destructive) */
i = 0;
while (i < yang_len_get(yn)){
ys = yn->ys_stmt[i];
switch (yang_keyword_get(ys)){
case Y_USES:
if (yang_expand_uses_node(yn, ys, i) < 0)
goto done;
break; /* Note same child is re-iterated since it may be changed */
default:
i++;
@ -630,10 +717,6 @@ yang_expand_grouping(yang_stmt *yn)
}
retval = 0;
done:
if (prefix)
free(prefix);
if (id)
free(id);
return retval;
}

View file

@ -199,11 +199,10 @@ if [ $BE -ne 0 ]; then
fi
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend"
wait_backend
fi
if [ $RC -ne 0 ]; then
new "kill old restconf daemon"
@ -211,10 +210,10 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon"
start_restconf -f $cfg
fi
new "wait restconf"
wait_restconf
fi
# mandatory-leaf See RFC7950 Sec 7.17
new "netconf set interface with augmented type and mandatory leaf"

View file

@ -30,6 +30,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config>
EOF

View file

@ -39,6 +39,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config>
EOF

View file

@ -41,6 +41,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config>
EOF

View file

@ -38,6 +38,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config>
EOF

View file

@ -186,7 +186,7 @@ nacm
# replace all, then must include NACM rules as well
# This usually triggers a 'HTTP/1.1 100 Continue' from curl as well
MSG="<data>$RULES</data>"
new "update root list permit"
new "update root list permit (trigger 100 Continue)"
expectpart "$(curl -u andy:bar $CURLOPTS -H 'Content-Type: application/yang-data+xml' -X PUT $RCPROTO://localhost/restconf/data -d "$MSG")" 0 'HTTP/1.1 204 No Content'
new "delete root list deny"

View file

@ -94,19 +94,19 @@ if [ $BE -ne 0 ]; then
new "start backend -s startup -f $cfg"
start_backend -s startup -f $cfg
fi
new "wait backend"
wait_backend
fi
new "$clixon_cli -D $DBG -1f $cfg -y $f show version"
expectpart "$($clixon_cli -D $DBG -1f $cfg show version)" 0 "${CLIXON_VERSION}"
new "$clixon_netconf -qf $cfg"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><interfaces xmlns=\"http://openconfig.net/yang/interfaces\"><interface><name>e</name><config><name>e</name><type>ex:eth</type><loopback-mode>false</loopback-mode><enabled>true</enabled></config><hold-time><config><up>0</up><down>0</down></config></hold-time><oc-eth:ethernet xmlns:oc-eth=\"http://openconfig.net/yang/interfaces/ethernet\"><oc-eth:config><oc-eth:auto-negotiate>true</oc-eth:auto-negotiate><oc-eth:enable-flow-control>false</oc-eth:enable-flow-control></oc-eth:config></oc-eth:ethernet></interface></interfaces></data></rpc-reply>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><interfaces xmlns=\"http://openconfig.net/yang/interfaces\"><interface><name>e</name><config><name>e</name><type>ex:eth</type><loopback-mode>false</loopback-mode><enabled>true</enabled></config><hold-time><config><up>0</up><down>0</down></config></hold-time></interface></interfaces></data></rpc-reply>]]>]]>"
new "cli show configuration"
expectpart "$($clixon_cli -1 -f $cfg show conf xml)" 0 "^<interfaces xmlns=\"http://openconfig.net/yang/interfaces\">" "<oc-eth:ethernet xmlns:oc-eth=\"http://openconfig.net/yang/interfaces/ethernet\">"
expectpart "$($clixon_cli -1 -f $cfg show conf xml)" 0 "^<interfaces xmlns=\"http://openconfig.net/yang/interfaces\">" --not-- "<oc-eth:ethernet xmlns:oc-eth=\"http://openconfig.net/yang/interfaces/ethernet\">"
new "cli set interfaces interface <tab> complete: e"
expectpart "$(echo "set interfaces interface " | $clixon_cli -f $cfg)" 0 "interface e"

View file

@ -140,13 +140,13 @@ new "Change type to atm"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>atm</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (mtu not allowed)"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented WHEN condition derived-from(type, \"ex:ethernet\") of node mtu in module example</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
new "Change type to ethernet (self)"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>ethernet</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (mtu not allowed on self)"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented WHEN condition derived-from(type, \"ex:ethernet\") of node mtu in module example</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from(type, \"ex:ethernet\")' of node 'mtu' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
@ -162,7 +162,7 @@ new "Change type to atm"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>atm</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"
new "netconf validate not OK (crc not allowed)"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented WHEN condition derived-from-or-self(type, \"ex:ethernet\") of node crc in module example</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$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-severity>error</error-severity><error-message>Failed augmented 'when' condition 'derived-from-or-self(type, \"ex:ethernet\")' of node 'crc' in module 'example'</error-message></rpc-error></rpc-reply>]]>]]>$"
new "Change type to ethernet (self)"
expecteof "$clixon_netconf -qf $cfg -D $DBG" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><interface xmlns=\"urn:example:clixon\"><name>e0</name><type>ethernet</type></interface></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>"

236
test/test_yang_when.sh Executable file
View file

@ -0,0 +1,236 @@
#!/usr/bin/env bash
# Tests for yang uses/augment + when
# Uses+when, test:
# 1. set/get/validate matching and non-matching when statement
# 2 RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list, the
# "uses" statement MUST NOT have a "when" statement.
# XXX:Originally for cli tests but not specific to cli (clean when done?)
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
fin=$dir/in
cfg=$dir/conf_yang.xml
fyang=$dir/example.yang
clidir=$dir/cli
if [ -d $clidir ]; then
rm -rf $clidir/*
else
mkdir $clidir
fi
# Use yang in example
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_GENMODEL>2</CLICON_CLI_GENMODEL>
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config>
EOF
cat <<EOF > $clidir/ex.cli
CLICON_MODE="example";
CLICON_PROMPT="%U@%H %W> ";
# Autocli syntax tree operations
edit @datamodel, cli_auto_edit("datamodel");
up, cli_auto_up("datamodel");
top, cli_auto_top("datamodel");
set @datamodel, cli_auto_set();
merge @datamodel, cli_auto_merge();
create @datamodel, cli_auto_create();
delete("Delete a configuration item") {
@datamodel, cli_auto_del();
all("Delete whole candidate configuration"), delete_all("candidate");
}
show("Show a particular state of the system"){
configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{
xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", false, false);
}
}
validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit();
EOF
function testrun()
{
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend"
wait_backend
# no match: name != kalle
new "netconf set non-match"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>paul</name></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf get default value not set"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><table xmlns=\"urn:example:clixon\"><parameter><name>paul</name></parameter></table></data></rpc-reply>]]>]]>$"
new "netconf set non-match value expect fail"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>paul</name><value>42</value></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>value</bad-element></error-info><error-severity>error</error-severity><error-message>Node 'value' tagged with 'when' condition 'ex:name = 'kalle'' in module 'example' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf get value without default"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><table xmlns=\"urn:example:clixon\"><parameter><name>paul</name></parameter></table></data></rpc-reply>]]>]]>$"
new "netconf validate default not set"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf discard non-match"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><discard-changes/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
# no match: name != kalle full put
new "netconf set non-match full"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>paul</name><value>42</value></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>value</bad-element></error-info><error-severity>error</error-severity><error-message>Node 'value' tagged with 'when' condition 'ex:name = 'kalle'' in module 'example' evaluates to false in edit-config operation (see RFC 7950 Sec 8.3.2)</error-message></rpc-error></rpc-reply>]]>]]>$"
new "netconf get value empty"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data/></rpc-reply>]]>]]>$"
# match: name = kalle
new "netconf set match"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf get default value set"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name><value>foo</value></parameter></table></data></rpc-reply>]]>]]>$"
new "netconf set non-match value expect ok"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name><value>42</value></parameter></table></config><default-operation>merge</default-operation></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf get value ok"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><table xmlns=\"urn:example:clixon\"><parameter><name>kalle</name><value>42</value></parameter></table></data></rpc-reply>]]>]]>$"
new "netconf validate default set"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "Kill backend"
# Check if premature kill
pid=$(pgrep -u root -f clixon_backend)
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
}
cat <<EOF > $fyang
module example {
namespace "urn:example:clixon";
prefix ex;
grouping value-top {
leaf value{
type string;
default foo;
}
}
container table{
list parameter{
key name;
leaf name{
type string;
}
uses value-top {
when "ex:name = 'kalle'";
}
}
}
}
EOF
new "1. uses/when test edit,default"
testrun
cat <<EOF > $fyang
module example {
namespace "urn:example:clixon";
prefix ex;
grouping value-top {
leaf value{
type string;
default foo;
}
}
container table{
list parameter{
key name;
leaf name{
type string;
}
}
}
augment /table/parameter {
uses value-top {
when "ex:name = 'kalle'";
}
}
}
EOF
new "2. augment/uses/when test edit,default (same but with augment)"
testrun
new "3. RFC 7950 7.21.5: If a key leaf is defined in a grouping that is used in a list"
# the "uses" statement MUST NOT have a "when" statement.
cat <<EOF > $fyang
module example {
namespace "urn:example:clixon";
prefix ex;
grouping value-top {
leaf name{
type string;
}
}
container table{
list parameter{
key name;
uses value-top {
when "ex:name = 'kalle'";
}
}
}
}
EOF
if [ ${valgrindtest} -eq 0 ]; then # Error dont cleanup mem OK
new "start backend -s init -f $cfg, expect yang when fail"
expectpart "$(sudo $clixon_backend -F -D $DBG -s init -f $cfg 2>&1)" 255 "Yang error: Key leaf 'name' defined in grouping 'value-top' is used in a 'uses' statement, This is not allowed according to RFC 7950 Sec 7.21.5"
fi
# kill backend
stop_backend -f $cfg
rm -rf $dir
new "endtest"
endtest