Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc.

This commit is contained in:
Olof hagsand 2019-01-04 11:37:26 +01:00
parent 7a8f242a09
commit 058a14579f
16 changed files with 412 additions and 86 deletions

View file

@ -79,6 +79,7 @@
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
### API changes on existing features (you may need to change your code) ### API changes on existing features (you may need to change your code)
* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced.
* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. * CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete.
* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file.
* For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily.
@ -94,6 +95,7 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes ### Minor changes
* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc.
* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC.
* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK.
* This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath`

View file

@ -768,6 +768,7 @@ yang2cli_stmt(clicon_handle h,
if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
goto done; goto done;
break; break;
case Y_CASE:
case Y_SUBMODULE: case Y_SUBMODULE:
case Y_MODULE: case Y_MODULE:
for (i=0; i<ys->ys_len; i++) for (i=0; i<ys->ys_len; i++)

View file

@ -80,7 +80,7 @@
* @param[in] cb Packet buffer * @param[in] cb Packet buffer
*/ */
static int static int
process_incoming_packet(clicon_handle h, netconf_input_packet(clicon_handle h,
cbuf *cb) cbuf *cb)
{ {
int retval = -1; int retval = -1;
@ -94,6 +94,8 @@ process_incoming_packet(clicon_handle h,
cxobj *xc; cxobj *xc;
yang_spec *yspec; yang_spec *yspec;
int ret; int ret;
cxobj *xa;
cxobj *xa2;
clicon_debug(1, "RECV"); clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
@ -110,7 +112,7 @@ process_incoming_packet(clicon_handle h,
/* Parse incoming XML message */ /* Parse incoming XML message */
if (xml_parse_string(str, yspec, &xreq) < 0){ if (xml_parse_string(str, yspec, &xreq) < 0){
free(str0); free(str0);
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0)
goto done; goto done;
netconf_output_encap(1, cbret, "rpc-error"); netconf_output_encap(1, cbret, "rpc-error");
goto done; goto done;
@ -143,8 +145,13 @@ process_incoming_packet(clicon_handle h,
goto done; goto done;
} }
else{ /* there is a return message in xret */ else{ /* there is a return message in xret */
cxobj *xa, *xa2;
assert(xret); if (xret == NULL){
if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0)
goto done;
netconf_output_encap(1, cbret, "rpc-error");
goto done;
}
if ((xc = xml_child_i(xret,0))!=NULL){ if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL; xa=NULL;
/* Copy message-id attribute from incoming to reply. /* Copy message-id attribute from incoming to reply.
@ -228,7 +235,7 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */ /* OK, we have an xml string from a client */
/* Remove trailer */ /* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (process_incoming_packet(h, cb) < 0) if (netconf_input_packet(h, cb) < 0)
; //goto done; // ignore errors ; //goto done; // ignore errors
if (cc_closed) if (cc_closed)
break; break;

View file

@ -314,7 +314,6 @@ netconf_edit_config(clicon_handle h,
enum operation_type operation = OP_MERGE; enum operation_type operation = OP_MERGE;
enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum test_option testopt = TEST_THEN_SET;/* only supports this */
enum error_option erropt = STOP_ON_ERROR; /* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */
cxobj *xc; /* config */
cxobj *x; cxobj *x;
cxobj *xfilter; cxobj *xfilter;
char *ftype = NULL; char *ftype = NULL;
@ -366,18 +365,8 @@ netconf_edit_config(clicon_handle h,
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto ok;
} }
/* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */
if ((xc = xpath_first(xn, "config")) != NULL){
#if 0
/* application-specific code registers 'config' */
if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){
goto ok;
}
#endif
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done; goto done;
}
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -715,8 +715,9 @@ text_modify(struct text_handle *th,
if (op==OP_NONE) if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
} }
/* First pass: mark existing children in base */ /* First pass: Loop through children of the x1 modification tree
/* Loop through children of the modification tree */ * collect matching nodes from x0 in x0vec (no changes to x0 children)
*/
if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){
clicon_err(OE_UNIX, errno, "calloc"); clicon_err(OE_UNIX, errno, "calloc");
goto done; goto done;
@ -732,17 +733,29 @@ text_modify(struct text_handle *th,
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
x0c = NULL; x0c = NULL;
if (match_base_child(x0, x1c, &x0c, yc) < 0) if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
x0vec[i++] = x0c; #if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
} }
/* Second pass: modify tree */ #endif
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
}
/* Second pass: Loop through children of the x1 modification tree again
* Now potentially modify x0:s children
* Here x0vec contains one-to-one matching nodes of x1:s children.
*/
x1c = NULL; x1c = NULL;
i = 0; i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c); x1cname = xml_name(x1c);
x0c = x0vec[i++];
yc = yang_find_datanode(y0, x1cname); yc = yang_find_datanode(y0, x1cname);
if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0) if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done; goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */ /* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0) if (ret == 0)
@ -862,8 +875,16 @@ text_modify_top(struct text_handle *th,
goto fail; goto fail;
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, &x0c, yc) < 0) if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
#if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done; goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */ /* If xml return - ie netconf error xml tree, then stop and return OK */

View file

@ -48,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword,
int upper); int upper);
cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
int xml_sort_verify(cxobj *x, void *arg); int xml_sort_verify(cxobj *x, void *arg);
int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp);
#endif /* _CLIXON_XML_SORT_H */ #endif /* _CLIXON_XML_SORT_H */

View file

@ -264,6 +264,7 @@ yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
char *yang_find_myprefix(yang_stmt *ys); char *yang_find_myprefix(yang_stmt *ys);
char *yang_find_mynamespace(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys);
yang_node *yang_choice(yang_stmt *y);
int yang_order(yang_stmt *y); int yang_order(yang_stmt *y);
int yang_print(FILE *f, yang_node *yn); int yang_print(FILE *f, yang_node *yn);
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);

View file

@ -103,7 +103,7 @@ object.
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;} #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;}
/* add _yy to error paramaters */ /* add _yy to error parameters */
#define YY_(msgid) msgid #define YY_(msgid) msgid
#include "clixon_config.h" #include "clixon_config.h"

View file

@ -189,13 +189,14 @@ xml2cli(FILE *f,
if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){
if (prepend0) if (prepend0)
fprintf(f, "%s", prepend0); fprintf(f, "%s", prepend0);
body = xml_body(x);
if (gt == GT_ALL || gt == GT_VARS) if (gt == GT_ALL || gt == GT_VARS)
fprintf(f, "%s ", xml_name(x)); fprintf(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){
if (index(body, ' ')) if (index(body, ' '))
fprintf(f, "\"%s\"", body); fprintf(f, "\"%s\"", body);
else else
fprintf(f, "%s", body); fprintf(f, "%s", body);
}
fprintf(f, "\n"); fprintf(f, "\n");
goto ok; goto ok;
} }
@ -508,40 +509,64 @@ xml_yang_validate_add(cxobj *xt,
int retval = -1; int retval = -1;
cg_var *cv = NULL; cg_var *cv = NULL;
char *reason = NULL; char *reason = NULL;
yang_stmt *yc; yang_stmt *yt; /* yang spec of xt going in */
yang_stmt *yc; /* yang spec of yt child */
yang_stmt *yx; /* yang spec of xt children */
yang_node *yp; /* yang spec of parent of yang spec of xt children */
int i; int i;
yang_stmt *ys;
char *body; char *body;
int ret; int ret;
cxobj *x; cxobj *x;
/* if not given by argument (overide) use default link /* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */ and !Node has a config sub-statement and it is false */
if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
switch (ys->ys_keyword){ switch (yt->ys_keyword){
case Y_RPC: case Y_RPC:
case Y_INPUT: case Y_INPUT:
case Y_LIST: case Y_LIST:
/* fall thru */ /* fall thru */
case Y_CONTAINER: case Y_CONTAINER:
for (i=0; i<ys->ys_len; i++){ for (i=0; i<yt->ys_len; i++){
yc = ys->ys_stmt[i]; yc = yt->ys_stmt[i];
if (yc->ys_keyword != Y_LEAF) switch (yc->ys_keyword){
continue; case Y_LEAF:
if (yang_config(yc)==0) if (yang_config(yc)==0)
continue; break;
if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0)
goto done; goto done;
goto fail; goto fail;
} }
break;
case Y_CHOICE:
if (yang_mandatory(yc)){
/* If there is one child with this choice as parent */
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((yx = xml_spec(x)) != NULL &&
(yp = yang_choice(yx)) != NULL &&
yp == (yang_node*)yc){
break; /* leave loop with x set */
}
}
if (x == NULL){ /* No hit */
if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0)
goto done;
goto fail;
}
}
break;
default:
break;
}
} }
break; break;
case Y_LEAF: case Y_LEAF:
/* fall thru */ /* fall thru */
case Y_LEAF_LIST: case Y_LEAF_LIST:
/* validate value against ranges, etc */ /* validate value against ranges, etc */
if ((cv = cv_dup(ys->ys_cv)) == NULL){ if ((cv = cv_dup(yt->ys_cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv_dup"); clicon_err(OE_UNIX, errno, "cv_dup");
goto done; goto done;
} }
@ -550,12 +575,12 @@ xml_yang_validate_add(cxobj *xt,
*/ */
if ((body = xml_body(xt)) != NULL){ if ((body = xml_body(xt)) != NULL){
if (cv_parse1(body, cv, &reason) != 1){ if (cv_parse1(body, cv, &reason) != 1){
if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0)
goto done; goto done;
goto fail; goto fail;
} }
if ((ys_cv_validate(cv, ys, &reason)) != 1){ if ((ys_cv_validate(cv, yt, &reason)) != 1){
if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0)
goto done; goto done;
goto fail; goto fail;
} }
@ -988,13 +1013,21 @@ xml_diff1(yang_stmt *ys,
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c));
goto done; goto done;
} }
if (match_base_child(x2, x1c, &x2c, yc) < 0) if (match_base_child(x2, x1c, yc, &x2c) < 0)
goto done; goto done;
if (x2c == NULL){ if (x2c == NULL){
if (cxvec_append(x1c, x1vec, x1veclen) < 0) if (cxvec_append(x1c, x1vec, x1veclen) < 0)
goto done; goto done;
} }
else{ else if (yang_choice(yc)){
/* if x1c and x2c are choice/case, then they are changed */
if (cxvec_append(x1c, changed_x1, changedlen) < 0)
goto done;
(*changedlen)--; /* append two vectors */
if (cxvec_append(x2c, changed_x2, changedlen) < 0)
goto done;
}
else{ /* if x1c and x2c are leafs w bodies, then they are changed */
if (yc->ys_keyword == Y_LEAF){ if (yc->ys_keyword == Y_LEAF){
if ((b1 = xml_body(x1c)) == NULL) /* empty type */ if ((b1 = xml_body(x1c)) == NULL) /* empty type */
break; break;
@ -1024,7 +1057,7 @@ xml_diff1(yang_stmt *ys,
clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c));
goto done; goto done;
} }
if (match_base_child(x1, x2c, &x1c, yc) < 0) if (match_base_child(x1, x2c, yc, &x1c) < 0)
goto done; goto done;
if (x1c == NULL) if (x1c == NULL)
if (cxvec_append(x2c, x2vec, x2veclen) < 0) if (cxvec_append(x2c, x2vec, x2veclen) < 0)
@ -1118,6 +1151,7 @@ yang2api_path_fmt_1(yang_stmt *ys,
yp->yn_keyword != Y_SUBMODULE){ yp->yn_keyword != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
goto done; goto done;
if (yp->yn_keyword != Y_CHOICE && yp->yn_keyword != Y_CASE)
cprintf(cb, "/"); cprintf(cb, "/");
} }
else /* top symbol - mark with name prefix */ else /* top symbol - mark with name prefix */
@ -1128,14 +1162,12 @@ yang2api_path_fmt_1(yang_stmt *ys,
cprintf(cb, "%s", ys->ys_argument); cprintf(cb, "%s", ys->ys_argument);
} }
else{ else{
#if 1
if (ys->ys_keyword == Y_LEAF && yp && if (ys->ys_keyword == Y_LEAF && yp &&
yp->yn_keyword == Y_LIST){ yp->yn_keyword == Y_LIST){
if (yang_key_match(yp, ys->ys_argument) == 0) if (yang_key_match(yp, ys->ys_argument) == 0)
cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */
} }
else else
#endif
if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE)
cprintf(cb, "%s", ys->ys_argument); cprintf(cb, "%s", ys->ys_argument);
} }
@ -2202,7 +2234,7 @@ xml_merge1(cxobj *x0,
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
x0c = NULL; x0c = NULL;
if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
goto done; goto done;
@ -2284,8 +2316,12 @@ xml_merge(cxobj *x0,
break; break;
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, &x0c, yc) < 0) if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
/* There is a case where x0c and x1c are choice nodes, if so,
* it is treated as a match, and x0c will remain
* If it is overwritten, then x0c should be removed here.
*/
if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0)
goto done; goto done;
if (*reason != NULL) if (*reason != NULL)

View file

@ -75,6 +75,8 @@
* @param[in] xp XML parent, can be NULL. * @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level) * @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found * @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
* @retval 0 OK
* @retval -1 Error
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node. * @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
* @note works for import prefix, but not work for generic XML parsing where * @note works for import prefix, but not work for generic XML parsing where
* xmlns and xmlns:ns are used. * xmlns and xmlns:ns are used.
@ -555,22 +557,18 @@ xml_sort_verify(cxobj *x0,
} }
/*! Given child tree x1c, find matching child in base tree x0 and return as x0cp /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp
* param[in] x0 Base tree node * @param[in] x0 Base tree node
* param[in] x1c Modification tree child * @param[in] x1c Modification tree child
* param[in] yc Yang spec of tree child * @param[in] yc Yang spec of tree child
* param[out] x0cp Matching base tree child (if any) * @param[out] x0cp Matching base tree child (if any)
* @note XXX: room for optimization? on 1K calls we have 1M body calls and * @retval 0 OK
500K xml_child_each/cvec_each calls. * @retval -1 Error
The outer loop is large for large lists
The inner loop is small
Major time in xml_find_body()
Can one do a binary search in the x0 list?
*/ */
int int
match_base_child(cxobj *x0, match_base_child(cxobj *x0,
cxobj *x1c, cxobj *x1c,
cxobj **x0cp, yang_stmt *yc,
yang_stmt *yc) cxobj **x0cp)
{ {
int retval = -1; int retval = -1;
cvec *cvk = NULL; /* vector of index keys */ cvec *cvk = NULL; /* vector of index keys */
@ -582,8 +580,29 @@ match_base_child(cxobj *x0,
char **keyvec = NULL; char **keyvec = NULL;
int i; int i;
int yorder; int yorder;
cxobj *x0c = NULL;
yang_stmt *y0c;
yang_node *y0p;
yang_node *yp; /* yang parent */
*x0cp = NULL; /* return value */ *x0cp = NULL; /* init return value */
#if 1
/* Special case is if yc parent (yp) is choice/case
* then find x0 child with same yc even though it does not match lexically
* However this will give another y0c != yc
*/
if ((yp = yang_choice(yc)) != NULL){
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) {
if ((y0c = xml_spec(x0c)) != NULL &&
(y0p = yang_choice(y0c)) != NULL &&
y0p == yp)
break; /* x0c will have a value */
}
*x0cp = x0c;
goto ok; /* What to do if not found? */
}
#endif
switch (yc->ys_keyword){ switch (yc->ys_keyword){
case Y_CONTAINER: /* Equal regardless */ case Y_CONTAINER: /* Equal regardless */
case Y_LEAF: /* Equal regardless */ case Y_LEAF: /* Equal regardless */
@ -629,11 +648,12 @@ match_base_child(cxobj *x0,
/* Get match. Sorting mode(optimized) or not?*/ /* Get match. Sorting mode(optimized) or not?*/
if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){
yorder = yang_order(yc); yorder = yang_order(yc);
*x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); x0c = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
} }
else{ else{
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); x0c = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
} }
*x0cp = x0c;
ok: ok:
retval = 0; retval = 0;
done: done:

View file

@ -95,7 +95,7 @@
#define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;} #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;}
/* add _yy to error paramaters */ /* add _yy to error parameters */
#define YY_(msgid) msgid #define YY_(msgid) msgid
#include "clixon_config.h" #include "clixon_config.h"

View file

@ -624,6 +624,28 @@ yang_find_mynamespace(yang_stmt *ys)
return namespace; return namespace;
} }
/*! If a given yang stmt has a choice/case as parent, return the choice statement
*/
yang_node *
yang_choice(yang_stmt *y)
{
yang_node *yp;
if ((yp = y->ys_parent) != NULL){
switch (yp->yn_keyword){
case Y_CHOICE:
return yp;
break;
case Y_CASE:
return yp->yn_parent;
break;
default:
break;
}
}
return NULL;
}
/*! Find matching y in yp:s children, return 0 and index or -1 if not found. /*! Find matching y in yp:s children, return 0 and index or -1 if not found.
* @retval 0 not found * @retval 0 not found
* @retval 1 found * @retval 1 found
@ -2685,7 +2707,7 @@ yang_mandatory(yang_stmt *ys)
{ {
yang_stmt *ym; yang_stmt *ym;
if (ys->ys_keyword != Y_LEAF) if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE)
return 0; return 0;
if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){
if (ym->ys_cv == NULL) /* shouldnt happen */ if (ym->ys_cv == NULL) /* shouldnt happen */

View file

@ -165,7 +165,7 @@
#define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_yang_parsetext, _YY->yy_linenum); YYERROR;} #define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_yang_parsetext, _YY->yy_linenum); YYERROR;}
/* add _yy to error paramaters */ /* add _yy to error parameters */
#define YY_(msgid) msgid #define YY_(msgid) msgid
#include "clixon_config.h" #include "clixon_config.h"

212
test/test_choice.sh Executable file
View file

@ -0,0 +1,212 @@
#!/bin/bash
# Choice type and mandatory
# Example from RFC7950 Sec 7.9.6
# Also test mandatory behaviour as in 7.6.5
# (XXX Would need default test in 7.6.4)
# Use-case: The ietf-netconf edit-config has a shorthand version of choice w mandatory:
# container { choice target { mandatory; leaf candidate; leaf running; }}
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/choice.xml
fyang=$dir/type.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>system</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
</config>
EOF
cat <<EOF > $fyang
module system{
yang-version 1.1;
namespace "urn:example:config";
prefix ex;
container system{
/* From RFC 7950 7.9.6 */
container protocol {
presence true;
choice name {
case a {
leaf udp {
type empty;
}
}
case b {
leaf tcp {
type empty;
}
}
}
}
/* Same but shorthand */
container shorthand {
presence true;
choice name {
leaf udp {
type empty;
}
leaf tcp {
type empty;
}
}
}
/* Same with mandatory true */
container mandatory {
presence true;
choice name {
mandatory true;
case a {
leaf udp {
type empty;
}
}
case b {
leaf tcp {
type empty;
}
}
}
}
}
}
EOF
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data &
# First vanilla (protocol) case
new "netconf validate empty"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set empty protocol"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate protocol"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf set protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><tcp/></protocol></system></data></rpc-reply>]]>]]>$'
new "netconf commit protocol tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><protocol><udp nc:operation="create"/></protocol></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get protocol udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><protocol><udp/></protocol></system></data></rpc-reply>]]>]]>$'
new "netconf commit protocol udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "restconf set protocol tcp"
expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":null}})" ""
new2 "restconf get protocol tcp"
expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" '{"system:system": {"protocol": {"tcp": null}}}
'
new "cli set protocol udp"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol udp" 0 "^$"
new "cli get protocol udp"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol udp$"
new "cli delete all"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o delete all" 0 "^$"
new "commit"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o commit" 0 "^$"
# Second shorthand (no case)
new "netconf set shorthand tcp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><tcp/></shorthand></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf changing from TCP to UDP (RFC7950 7.9.6)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></config></edit-config></rpc>]]>]]>' '^<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get shorthand udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><shorthand><udp/></shorthand></system></data></rpc-reply>]]>]]>$'
new "netconf validate shorthand"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Third check mandatory
new "netconf set empty mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory/></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get mandatory empty"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory/></system></data></rpc-reply>]]>]]>$'
new "netconf validate mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>name</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>$'
new "netconf set mandatory udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get mandatory udp"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><system xmlns="urn:example:config"><mandatory><udp/></mandatory></system></data></rpc-reply>]]>]]>$'
new "netconf validate mandatory"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -eq 0 ]; then
exit # BE
fi
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
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

View file

@ -88,7 +88,7 @@ if [ $BE -ne 0 ]; then
fi fi
new "leafref base config" new "leafref base config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name><type>ex:eth</type> <ipv4><address><ip>192.0.2.1</ip></address><address><ip>192.0.2.2</ip></address></ipv4></interface><interface><name>lo</name><type>ex:lo</type><ipv4><address><ip>127.0.0.1</ip></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name><type>ex:eth</type><ipv4><address><ip>192.0.2.1</ip><prefix-length>24</prefix-length></address><address><ip>192.0.2.2</ip><prefix-length>24</prefix-length></address></ipv4></interface><interface><name>lo</name><type>ex:lo</type><ipv4><address><ip>127.0.0.1</ip><prefix-length>32</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "leafref get config" new "leafref get config"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name>' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth0</name>'

View file

@ -115,13 +115,28 @@ expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http://
new2 "restconf wrong method" new2 "restconf wrong method"
expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} '
new2 "restconf edit-config missing mandatory" new2 "restconf example missing input"
expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} '
new "netconf kill-session missing session-id mandatory" new "netconf kill-session missing session-id mandatory"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$' expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><kill-session/></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>session-id</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory variable</error-message></rpc-error></rpc-reply>]]>]]>$'
# edit-config? new "netconf edit-config ok"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit-config extra arg should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target><extra/><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>extra</bad-element></error-info><error-severity>error</error-severity></rpc-error></rpc-reply>]]>]]>$"
new "netconf edit-config empty target should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target/><config/></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>config-target</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>
$'
new "netconf edit-config missing target should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><config/></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-tag>missing-element</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-info><bad-element>target</bad-element></error-info></rpc-error></rpc-reply>]]>]]>$"
new "netconf edit-config missing config should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><target><candidate/></target></edit-config></rpc>]]>]]>' '<rpc-reply><rpc-error><error-type>application</error-type><error-tag>missing-element</error-tag><error-info><bad-element>edit-content</bad-element></error-info><error-severity>error</error-severity><error-message>Mandatory choice</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Kill restconf daemon" new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf" sudo pkill -u www-data -f "/www-data/clixon_restconf"