From 058a14579f4969258de84c4f82d76fd3c1b6e1d7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 4 Jan 2019 11:37:26 +0100 Subject: [PATCH] Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. --- CHANGELOG.md | 2 + apps/cli/cli_generate.c | 1 + apps/netconf/netconf_main.c | 17 ++- apps/netconf/netconf_rpc.c | 15 +- datastore/text/clixon_xmldb_text.c | 35 ++++- lib/clixon/clixon_xml_sort.h | 2 +- lib/clixon/clixon_yang.h | 1 + lib/src/clixon_json_parse.y | 2 +- lib/src/clixon_xml_map.c | 106 ++++++++++----- lib/src/clixon_xml_sort.c | 56 +++++--- lib/src/clixon_xpath_parse.y | 2 +- lib/src/clixon_yang.c | 24 +++- lib/src/clixon_yang_parse.y | 2 +- test/test_choice.sh | 212 +++++++++++++++++++++++++++++ test/test_leafref.sh | 2 +- test/test_rpc.sh | 19 ++- 16 files changed, 412 insertions(+), 86 deletions(-) create mode 100755 test/test_choice.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 588c5e10..58ca127d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * 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) +* 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. * 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. @@ -94,6 +95,7 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### 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 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` diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 8c87a0d7..b91a5242 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -768,6 +768,7 @@ yang2cli_stmt(clicon_handle h, if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) goto done; break; + case Y_CASE: case Y_SUBMODULE: case Y_MODULE: for (i=0; iys_len; i++) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index eadbcc1b..7e1163dc 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -80,7 +80,7 @@ * @param[in] cb Packet buffer */ static int -process_incoming_packet(clicon_handle h, +netconf_input_packet(clicon_handle h, cbuf *cb) { int retval = -1; @@ -94,6 +94,8 @@ process_incoming_packet(clicon_handle h, cxobj *xc; yang_spec *yspec; int ret; + cxobj *xa; + cxobj *xa2; clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); @@ -110,7 +112,7 @@ process_incoming_packet(clicon_handle h, /* Parse incoming XML message */ if (xml_parse_string(str, yspec, &xreq) < 0){ free(str0); - if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0) goto done; netconf_output_encap(1, cbret, "rpc-error"); goto done; @@ -143,8 +145,13 @@ process_incoming_packet(clicon_handle h, goto done; } 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){ xa=NULL; /* 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 */ /* Remove trailer */ *(((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 if (cc_closed) break; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 0770a0b9..21a02193 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -314,7 +314,6 @@ netconf_edit_config(clicon_handle h, enum operation_type operation = OP_MERGE; enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */ - cxobj *xc; /* config */ cxobj *x; cxobj *xfilter; char *ftype = NULL; @@ -366,18 +365,8 @@ netconf_edit_config(clicon_handle h, ""); 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) - goto done; - } + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; ok: retval = 0; done: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index c4d2d1b4..689e9a4b 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -715,8 +715,9 @@ text_modify(struct text_handle *th, if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } - /* First pass: mark existing children in base */ - /* Loop through children of the modification tree */ + /* First pass: Loop through children of the x1 modification tree + * collect matching nodes from x0 in x0vec (no changes to x0 children) + */ if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; @@ -732,17 +733,29 @@ text_modify(struct text_handle *th, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) 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; + } +#endif + x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */ } - /* Second pass: modify tree */ + /* 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; i = 0; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + x0c = x0vec[i++]; 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; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (ret == 0) @@ -862,8 +875,16 @@ text_modify_top(struct text_handle *th, goto fail; } /* 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; +#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) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 83f94f44..8b1e6f82 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -48,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); 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 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 */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index fb727dfd..b0635385 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -264,6 +264,7 @@ yang_stmt *yang_find_datanode(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_mynamespace(yang_stmt *ys); +yang_node *yang_choice(yang_stmt *y); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index a6533a06..07da89a4 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -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;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 660c4849..4b51e30b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -189,13 +189,14 @@ xml2cli(FILE *f, if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); - body = xml_body(x); if (gt == GT_ALL || gt == GT_VARS) fprintf(f, "%s ", xml_name(x)); - if (index(body, ' ')) - fprintf(f, "\"%s\"", body); - else - fprintf(f, "%s", body); + if ((body = xml_body(x)) != NULL){ + if (index(body, ' ')) + fprintf(f, "\"%s\"", body); + else + fprintf(f, "%s", body); + } fprintf(f, "\n"); goto ok; } @@ -508,32 +509,56 @@ xml_yang_validate_add(cxobj *xt, int retval = -1; cg_var *cv = 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; - 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){ + if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + switch (yt->ys_keyword){ case Y_RPC: case Y_INPUT: case Y_LIST: /* fall thru */ case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = ys->ys_stmt[i]; - if (yc->ys_keyword != Y_LEAF) - continue; - if (yang_config(yc)==0) - continue; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; + for (i=0; iys_len; i++){ + yc = yt->ys_stmt[i]; + switch (yc->ys_keyword){ + case Y_LEAF: + if (yang_config(yc)==0) + break; + if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + 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; @@ -541,7 +566,7 @@ xml_yang_validate_add(cxobj *xt, /* fall thru */ case Y_LEAF_LIST: /* 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"); goto done; } @@ -550,12 +575,12 @@ xml_yang_validate_add(cxobj *xt, */ if ((body = xml_body(xt)) != NULL){ 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 fail; } - if ((ys_cv_validate(cv, ys, &reason)) != 1){ - if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + if ((ys_cv_validate(cv, yt, &reason)) != 1){ + if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0) goto done; goto fail; } @@ -948,12 +973,12 @@ cvec2xml_1(cvec *cvv, } /*! Recursive help function to compute differences between two xml trees - * @param[in] x1 First XML tree - * @param[in] x2 Second XML tree + * @param[in] x1 First XML tree + * @param[in] x2 Second XML tree * @param[out] x1vec Pointervector to XML nodes existing in only first tree * @param[out] x1veclen Length of first vector - * @param[out] x2vec Pointervector to XML nodes existing in only second tree - * @param[out] x2veclen Length of x2vec vector + * @param[out] x2vec Pointervector to XML nodes existing in only second tree + * @param[out] x2veclen Length of x2vec vector * @param[out] changed_x1 Pointervector to XML nodes changed orig value * @param[out] changed_x2 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector @@ -988,13 +1013,21 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; } - if (match_base_child(x2, x1c, &x2c, yc) < 0) + if (match_base_child(x2, x1c, yc, &x2c) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) 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 ((b1 = xml_body(x1c)) == NULL) /* empty type */ break; @@ -1024,7 +1057,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; } - if (match_base_child(x1, x2c, &x1c, yc) < 0) + if (match_base_child(x1, x2c, yc, &x1c) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1118,7 +1151,8 @@ yang2api_path_fmt_1(yang_stmt *ys, yp->yn_keyword != Y_SUBMODULE){ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; - cprintf(cb, "/"); + if (yp->yn_keyword != Y_CHOICE && yp->yn_keyword != Y_CASE) + cprintf(cb, "/"); } else /* top symbol - mark with name prefix */ cprintf(cb, "/%s:", yp->yn_argument); @@ -1128,14 +1162,12 @@ yang2api_path_fmt_1(yang_stmt *ys, cprintf(cb, "%s", ys->ys_argument); } else{ -#if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else -#endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) 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 */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2284,8 +2316,12 @@ xml_merge(cxobj *x0, break; } /* 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; + /* 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) goto done; if (*reason != NULL) diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 7f08b35e..fb5e412b 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -75,6 +75,8 @@ * @param[in] xp XML parent, can be NULL. * @param[in] yspec Yang specification (top level) * @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 ,look for top "foo" node. * @note works for import prefix, but not work for generic XML parsing where * 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 - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child - * param[out] x0cp Matching base tree child (if any) - * @note XXX: room for optimization? on 1K calls we have 1M body calls and - 500K xml_child_each/cvec_each calls. - 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? -*/ + * @param[in] x0 Base tree node + * @param[in] x1c Modification tree child + * @param[in] yc Yang spec of tree child + * @param[out] x0cp Matching base tree child (if any) + * @retval 0 OK + * @retval -1 Error + */ int -match_base_child(cxobj *x0, - cxobj *x1c, - cxobj **x0cp, - yang_stmt *yc) +match_base_child(cxobj *x0, + cxobj *x1c, + yang_stmt *yc, + cxobj **x0cp) { int retval = -1; cvec *cvk = NULL; /* vector of index keys */ @@ -582,8 +580,29 @@ match_base_child(cxobj *x0, char **keyvec = NULL; int i; 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){ case Y_CONTAINER: /* Equal regardless */ case Y_LEAF: /* Equal regardless */ @@ -629,11 +648,12 @@ match_base_child(cxobj *x0, /* Get match. Sorting mode(optimized) or not?*/ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ 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{ - *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: retval = 0; done: diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index b121cc2c..db672129 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -95,7 +95,7 @@ #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 #include "clixon_config.h" diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 36404682..b618f037 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -624,6 +624,28 @@ yang_find_mynamespace(yang_stmt *ys) 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. * @retval 0 not found * @retval 1 found @@ -2685,7 +2707,7 @@ yang_mandatory(yang_stmt *ys) { yang_stmt *ym; - if (ys->ys_keyword != Y_LEAF) + if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE) return 0; if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ if (ym->ys_cv == NULL) /* shouldnt happen */ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 9ffac45e..aef0cc07 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -165,7 +165,7 @@ #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 #include "clixon_config.h" diff --git a/test/test_choice.sh b/test/test_choice.sh new file mode 100755 index 00000000..c2b2f91f --- /dev/null +++ b/test/test_choice.sh @@ -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 < $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + system + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + false + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +cat < $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 "]]>]]>" "^]]>]]>$" + +new "netconf set empty protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf validate protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf set protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +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 ']]>]]>' "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get shorthand udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate shorthand" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +# Third check mandatory +new "netconf set empty mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory choice]]>]]>$' + +new "netconf set mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +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 diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 63c6a270..4bb83b5c 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,7 +88,7 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.124
192.0.2.224
loex:lo
127.0.0.132
]]>]]>' '^]]>]]>$' new "leafref get config" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f033dfb0..45b357f1 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -115,13 +115,28 @@ expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http:// 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"}}} ' -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"}}} ' + new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' -# edit-config? +new "netconf edit-config ok" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" + +new "netconf edit-config extra arg should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerror]]>]]>$" + +new "netconf edit-config empty target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementconfig-targeterrorMandatory choice]]>]]> +$' + +new "netconf edit-config missing target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^missing-elementprotocolerrortarget]]>]]>$" + +new "netconf edit-config missing config should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'applicationmissing-elementedit-contenterrorMandatory choice]]>]]>$' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf"