* More precise Yang validation and better error messages
* For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed * Removed delete-config support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Some restconf error messages contained "rpc-reply" or "rpc-error" which have now been removed. * Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47)
This commit is contained in:
parent
03e618b1e5
commit
f872c7e295
45 changed files with 807 additions and 405 deletions
|
|
@ -87,6 +87,7 @@
|
|||
#include "clixon_xpath.h"
|
||||
#include "clixon_log.h"
|
||||
#include "clixon_err.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_xml_sort.h"
|
||||
#include "clixon_xml_map.h"
|
||||
|
||||
|
|
@ -230,10 +231,15 @@ xml2cli(FILE *f,
|
|||
/*! Validate xml node of type leafref, ensure the value is one of that path's reference
|
||||
* @param[in] xt XML leaf node of type leafref
|
||||
* @param[in] ytype Yang type statement belonging to the XML node
|
||||
* @param[out] cbret Error buffer
|
||||
* @retval 1 Validation OK
|
||||
* @retval 0 Validation failed
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
validate_leafref(cxobj *xt,
|
||||
yang_stmt *ytype)
|
||||
yang_stmt *ytype,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ypath;
|
||||
|
|
@ -247,8 +253,9 @@ validate_leafref(cxobj *xt,
|
|||
if ((leafrefbody = xml_body(xt)) == NULL)
|
||||
goto ok;
|
||||
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
|
||||
goto done;
|
||||
if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Leafref requires path statement") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 0)
|
||||
goto done;
|
||||
|
|
@ -260,9 +267,9 @@ validate_leafref(cxobj *xt,
|
|||
break;
|
||||
}
|
||||
if (i==xlen){
|
||||
clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s",
|
||||
leafrefbody);
|
||||
goto done;
|
||||
if (netconf_bad_element(cbret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
|
|
@ -270,6 +277,9 @@ validate_leafref(cxobj *xt,
|
|||
if (xvec)
|
||||
free(xvec);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Validate xml node of type identityref, ensure value is a defined identity
|
||||
|
|
@ -285,14 +295,18 @@ validate_leafref(cxobj *xt,
|
|||
* @param[in] xt XML leaf node of type identityref
|
||||
* @param[in] ys Yang spec of leaf
|
||||
* @param[in] ytype Yang type field of type identityref
|
||||
* @param[out] cbret Error buffer
|
||||
* @retval 1 Validation OK
|
||||
* @retval 0 Validation failed
|
||||
* @retval -1 Error
|
||||
* @see ys_populate_identity where the derived types are set
|
||||
* @see RFC7950 Sec 9.10.2:
|
||||
|
||||
*/
|
||||
static int
|
||||
validate_identityref(cxobj *xt,
|
||||
yang_stmt *ys,
|
||||
yang_stmt *ytype)
|
||||
yang_stmt *ytype,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
char *node;
|
||||
|
|
@ -305,37 +319,46 @@ validate_identityref(cxobj *xt,
|
|||
* Always add default prefix because derived identifiers are stored with
|
||||
* prefixes in the base identifiers derived-list.
|
||||
*/
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
if ((node = xml_body(xt)) == NULL)
|
||||
return 0;
|
||||
if (strchr(node, ':') == NULL){
|
||||
prefix = yang_find_myprefix(ys);
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "%s:%s", prefix, node);
|
||||
node = cbuf_get(cb);
|
||||
}
|
||||
/* This is the type's base reference */
|
||||
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){
|
||||
clicon_err(OE_DB, 0, "Identityref validation failed, no base");
|
||||
goto done;
|
||||
if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Identityref validation failed, no base") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* This is the actual base identity */
|
||||
if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){
|
||||
clicon_err(OE_DB, 0, "Identityref validation failed, no base identity");
|
||||
goto done;
|
||||
if (netconf_missing_element(cbret, "application", ybaseref->ys_argument, "Identityref validation failed, no base identity") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
/* Here check if node is in the derived node list of the base identity */
|
||||
if (cvec_find(ybaseid->ys_cvec, node) == NULL){
|
||||
clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument);
|
||||
goto done;
|
||||
cbuf_reset(cb);
|
||||
cprintf(cb, "Identityref validation failed, %s not derived from %s",
|
||||
node, ybaseid->ys_argument);
|
||||
if (netconf_operation_failed(cbret, "application", cbuf_get(cb)) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
retval = 0;
|
||||
retval = 1;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Validate an RPC node
|
||||
|
|
@ -377,24 +400,33 @@ validate_identityref(cxobj *xt,
|
|||
* the same order as they are defined within the "output" statement.
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_rpc(cxobj *xrpc)
|
||||
xml_yang_validate_rpc(cxobj *xrpc,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yn=NULL; /* rpc name */
|
||||
cxobj *xn; /* rpc name */
|
||||
yang_stmt *yi=NULL; /* input name */
|
||||
cxobj *xi; /* input name */
|
||||
int ret;
|
||||
|
||||
assert(strcmp(xml_name(xrpc), "rpc")==0);
|
||||
if (strcmp(xml_name(xrpc), "rpc")){
|
||||
clicon_err(OE_XML, EINVAL, "Expected RPC");
|
||||
goto done;
|
||||
}
|
||||
xn = NULL;
|
||||
/* xn is name of rpc, ie <rcp><xn/></rpc> */
|
||||
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
|
||||
if ((yn = xml_spec(xn)) == NULL)
|
||||
goto fail;
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) {
|
||||
if ((yi = xml_spec(xi)) == NULL)
|
||||
goto fail;
|
||||
}
|
||||
if ((ret = xml_yang_validate_all(xn, cbret)) < 0)
|
||||
goto fail;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
if ((ret = xml_yang_validate_add(xn, cbret)) < 0)
|
||||
goto fail;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
// ok: /* pass validation */
|
||||
retval = 1;
|
||||
|
|
@ -408,14 +440,24 @@ xml_yang_validate_rpc(cxobj *xrpc)
|
|||
/*! Validate a single XML node with yang specification for added entry
|
||||
* 1. Check if mandatory leafs present as subs.
|
||||
* 2. Check leaf values, eg int ranges and string regexps.
|
||||
* @param[in] xt XML node to be validated
|
||||
* @retval 0 Valid OK
|
||||
* @retval -1 Validation failed
|
||||
* @param[in] xt XML node to be validated
|
||||
* @param[out] cbret Error buffer
|
||||
* @retval 1 Validation OK
|
||||
* @retval 0 Validation failed
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
* cxobj *x;
|
||||
* cbuf *cbret = cbuf_new();
|
||||
* if ((ret = xml_yang_validate_add(x, cbret)) < 0)
|
||||
* err;
|
||||
* if (ret == 0)
|
||||
* fail;
|
||||
* @endcode
|
||||
* @see xml_yang_validate_all
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_add(cxobj *xt,
|
||||
void *arg)
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
cg_var *cv = NULL;
|
||||
|
|
@ -424,11 +466,14 @@ xml_yang_validate_add(cxobj *xt,
|
|||
int i;
|
||||
yang_stmt *ys;
|
||||
char *body;
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
/* if not given by argument (overide) use default link
|
||||
and !Node has a config sub-statement and it is false */
|
||||
if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){
|
||||
switch (ys->ys_keyword){
|
||||
case Y_RPC:
|
||||
case Y_INPUT:
|
||||
case Y_LIST:
|
||||
/* fall thru */
|
||||
|
|
@ -440,9 +485,9 @@ xml_yang_validate_add(cxobj *xt,
|
|||
if (yang_config(yc)==0)
|
||||
continue;
|
||||
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
|
||||
clicon_err(OE_CFG, 0,"Missing mandatory variable: %s",
|
||||
yc->ys_argument);
|
||||
goto done;
|
||||
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -463,12 +508,11 @@ xml_yang_validate_add(cxobj *xt,
|
|||
goto done;
|
||||
}
|
||||
if ((ys_cv_validate(cv, ys, &reason)) != 1){
|
||||
clicon_err(OE_DB, 0,
|
||||
"validation of %s failed %s",
|
||||
ys->ys_argument, reason?reason:"");
|
||||
if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0)
|
||||
goto done;
|
||||
if (reason)
|
||||
free(reason);
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -476,28 +520,43 @@ xml_yang_validate_add(cxobj *xt,
|
|||
break;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_yang_validate_add(x, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
if (cv)
|
||||
cv_free(cv);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Validate a single XML node with yang specification for all (not only added) entries
|
||||
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
|
||||
* @param[in] xt XML node to be validated
|
||||
* @param[in] arg Not used
|
||||
* @retval -1 Validation failed
|
||||
* @retval 0 Validation OK
|
||||
* @param[out] cbret Error buffer
|
||||
* @retval 1 Validation OK
|
||||
* @retval 0 Validation failed
|
||||
* @retval -1 Error
|
||||
* @see xml_yang_validate_add
|
||||
* @code
|
||||
* if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0)
|
||||
* cxobj *x;
|
||||
* cbuf *cbret = cbuf_new();
|
||||
* if ((ret = xml_yang_validate_all(x, cbret)) < 0)
|
||||
* err;
|
||||
* if (ret == 0)
|
||||
* fail;
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_all(cxobj *xt,
|
||||
void *arg)
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ys; /* yang node */
|
||||
|
|
@ -505,13 +564,24 @@ xml_yang_validate_all(cxobj *xt,
|
|||
yang_stmt *ye; /* yang must error-message */
|
||||
char *xpath;
|
||||
int nr;
|
||||
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
/* if not given by argument (overide) use default link
|
||||
and !Node has a config sub-statement and it is false */
|
||||
if ((ys = xml_spec(xt)) != NULL &&
|
||||
yang_config(ys) != 0){
|
||||
ys=xml_spec(xt);
|
||||
if (ys==NULL){
|
||||
if (netconf_unknown_element(cbret, "application", xml_name(xt), NULL) < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (ys != NULL && yang_config(ys) != 0){
|
||||
/* Node-specific validation */
|
||||
switch (ys->ys_keyword){
|
||||
case Y_ANYXML:
|
||||
case Y_ANYDATA:
|
||||
goto ok;
|
||||
break;
|
||||
case Y_LEAF:
|
||||
/* fall thru */
|
||||
case Y_LEAF_LIST:
|
||||
|
|
@ -520,11 +590,11 @@ xml_yang_validate_all(cxobj *xt,
|
|||
*/
|
||||
if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){
|
||||
if (strcmp(yc->ys_argument, "leafref") == 0){
|
||||
if (validate_leafref(xt, yc) < 0)
|
||||
if (validate_leafref(xt, yc, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (strcmp(yc->ys_argument, "identityref") == 0){
|
||||
if (validate_identityref(xt, ys, yc) < 0)
|
||||
if (validate_identityref(xt, ys, yc, cbret) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
|
@ -568,11 +638,11 @@ xml_yang_validate_all(cxobj *xt,
|
|||
if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0)
|
||||
goto done;
|
||||
if (!nr){
|
||||
if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL)
|
||||
clicon_err(OE_DB, 0, "%s", ye->ys_argument);
|
||||
else
|
||||
clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
|
||||
goto done;
|
||||
ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL);
|
||||
if (netconf_operation_failed(cbret, "application",
|
||||
ye?ye->ys_argument:"must xpath validation failed") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
/* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */
|
||||
|
|
@ -581,14 +651,42 @@ xml_yang_validate_all(cxobj *xt,
|
|||
if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0)
|
||||
goto done;
|
||||
if (!nr){
|
||||
clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt));
|
||||
goto done;
|
||||
if (netconf_operation_failed(cbret, "application",
|
||||
"when xpath validation failed") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_yang_validate_all(x, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
int
|
||||
xml_yang_validate_all_top(cxobj *xt,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int ret;
|
||||
cxobj *x;
|
||||
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
|
||||
if ((ret = xml_yang_validate_all(x, cbret)) < 1)
|
||||
return ret;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*! Translate a single xml node to a cligen variable vector. Note not recursive
|
||||
|
|
@ -1310,6 +1408,11 @@ xml_tree_prune_flagged(cxobj *xt,
|
|||
|
||||
/*! Add default values (if not set)
|
||||
* @param[in] xt XML tree with some node marked
|
||||
* @param[in] arg Ignored
|
||||
* Typically called in a recursive apply function:
|
||||
* @code
|
||||
* xml_apply(xt, CX_ELMNT, xml_default, NULL);
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_default(cxobj *xt,
|
||||
|
|
@ -1328,7 +1431,8 @@ xml_default(cxobj *xt,
|
|||
goto done;
|
||||
}
|
||||
/* Check leaf defaults */
|
||||
if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){
|
||||
if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST ||
|
||||
ys->ys_keyword == Y_INPUT){
|
||||
for (i=0; i<ys->ys_len; i++){
|
||||
y = ys->ys_stmt[i];
|
||||
if (y->ys_keyword != Y_LEAF)
|
||||
|
|
@ -1493,9 +1597,9 @@ xml_spec_populate_rpc(clicon_handle h,
|
|||
yang_stmt *y=NULL; /* yang node */
|
||||
yang_stmt *ymod=NULL; /* yang module */
|
||||
yang_stmt *yi = NULL; /* input */
|
||||
yang_stmt *ya = NULL; /* arg */
|
||||
// yang_stmt *ya = NULL; /* arg */
|
||||
cxobj *x;
|
||||
cxobj *xi;
|
||||
// cxobj *xi;
|
||||
int i;
|
||||
|
||||
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
|
||||
|
|
@ -1521,11 +1625,18 @@ xml_spec_populate_rpc(clicon_handle h,
|
|||
if (y){
|
||||
xml_spec_set(x, y);
|
||||
if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){
|
||||
/* kludge rpc -> input */
|
||||
xml_spec_set(x, yi);
|
||||
#if 1
|
||||
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
|
||||
goto done;
|
||||
#else
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) {
|
||||
if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL)
|
||||
xml_spec_set(xi, ya);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue