* Replaced separate autocli trees with a single @basemodel tree by using filter labels

* Added lastkey argument to yang_key_match()
* Fixed segv in process-sigchld
* Added ietf-yang-library to CLICON_CLI_AUTOCLI_EXCLUDE default value
* Added yang_spec_print() function
This commit is contained in:
Olof hagsand 2021-12-15 14:23:05 +01:00
parent 87d243d7e5
commit f8f34e3571
17 changed files with 666 additions and 192 deletions

View file

@ -35,6 +35,22 @@
## 5.5.0 ## 5.5.0
Planned: January, 2022 Planned: January, 2022
### New features
* Changed auto-cli design
* Replaced separet autocli trees with a single `@basemodel` tree by using filter labels
* Filter labels are added to the fill tree and then filtered out using `@remove:<label>`
* Labels include: termfirstkeys, termlist, termleaf, leafvar, nonconfig,
* For detailed docs see yang2cli_post()
* This method reduces memory usage and is more generic
* Backward compatible: can continue use the "old" trees.
* Translation to new method as follows:
* `@datamodel` translated to `@basemodel, @remove:termfirstkeys, @remove:termlist, @remove:termleaf, @remove:nonconfig`
* `@datamodelshow` translated to `@basemodel, @remove:leafvar, @remove:nonconfig`
* `@datamodelstate` translated to `@basemodel, @remove:leafvar`
* Note: autocli mode support is not backward compatible
* see main example
### Minor features ### Minor features
* New `clixon-dev` development container (Work-in-progress) * New `clixon-dev` development container (Work-in-progress)
@ -70,12 +86,13 @@ This release features lots of minor updates and bugfixes, an updated list pagina
Users may have to change how they access the system Users may have to change how they access the system
* Optional yangs for testing have been removed from the Clixon repo * Optional yangs for testing have been removed from the Clixon repo
* These were included for testing * As a consequence, the following configure options have been removed:
* If you want to run the Clixon test suite you need to point `YANGMODELS`, see test/README.md
* The following configure options have been removed:
* `configure --with-opt-yang-installdir=DIR` * `configure --with-opt-yang-installdir=DIR`
* `configure --enable-optyangs` * `configure --enable-optyangs`
* You may need to specify standard YANGs using configure option `--with-yang-standard-dir=DIR` * You may need to specify where standard IETFC/IEEE YANGMODELS are
* Note, this applies to testing and main example only, not core clixon
* The following configure option has been added
* `configure --with-yang-standard-dir=DIR`
* RPC replies now verified with YANG * RPC replies now verified with YANG
* Stricter checking of outgoing RPC replies from server * Stricter checking of outgoing RPC replies from server
* See [RPC output not verified by yang](https://github.com/clicon/clixon/issues/283) * See [RPC output not verified by yang](https://github.com/clicon/clixon/issues/283)

View file

@ -383,7 +383,7 @@ cli_xml2cli(cxobj *xn,
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){
xe = NULL; xe = NULL;
while ((xe = xml_child_each(xn, xe, -1)) != NULL){ while ((xe = xml_child_each(xn, xe, -1)) != NULL){
if ((match = yang_key_match(ys, xml_name(xe))) < 0) if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done; goto done;
if (!match) if (!match)
continue; continue;
@ -415,7 +415,7 @@ cli_xml2cli(cxobj *xn,
xe = NULL; xe = NULL;
while ((xe = xml_child_each(xn, xe, -1)) != NULL){ while ((xe = xml_child_each(xn, xe, -1)) != NULL){
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){
if ((match = yang_key_match(ys, xml_name(xe))) < 0) if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done; goto done;
if (match) if (match)
continue; /* Not key itself */ continue; /* Not key itself */
@ -545,10 +545,14 @@ cli_auto_up(clicon_handle h,
} }
if ((co0 = cligen_ph_workpoint_get(ph)) == NULL) if ((co0 = cligen_ph_workpoint_get(ph)) == NULL)
goto ok; goto ok;
co1 = co_up(co0);
/* Find parent that has a callback */ /* Find parent that has a callback */
while (co1 && (co1->co_callbacks == NULL)) for (co1 = co_up(co0); co1; co1 = co_up(co1)){
co1 = co_up(co1); cg_obj *cot = NULL;
if (co_terminal(co1, &cot)){
if (cot == NULL || co_isfilter(cot->co_cvec, "termlist") == 0)
break; /* found */
}
}
cligen_ph_workpoint_set(ph, co1); cligen_ph_workpoint_set(ph, co1);
if (co1 == NULL){ if (co1 == NULL){
clicon_data_set(h, "cli-edit-mode", ""); clicon_data_set(h, "cli-edit-mode", "");

View file

@ -42,6 +42,17 @@
* YANG generate CLI * YANG generate CLI
* A special tree called @datamodel is generated by the yang2cli function.
* This tree contains generated CLIgen syntax for all loaded YANG modules, except the ones given by
* CLICON_CLI_AUTOCLI_EXCLUDE
* The @datamodel tree can be used using the CLIgen "tree reference" functionality as described in
* the cligen tutorial Secion 2.7.
* The tree can be modified by removing labels.
* By default "nonconfig" and "show" labels are by default removed.
* This means that using @datamodel without modifiers is a "clean" config tree.
* "nonconfig" and "show" can be removed by using, eg
* cmd @basemodel, @remove:show, @remove:nonconfig, callback();
This is an example yang module: This is an example yang module:
module m { module m {
container x { container x {
@ -71,7 +82,6 @@ You can see which CLISPEC it generates via clixon_cli -D 2:
#include "clixon_config.h" /* generated by config & autoconf */ #include "clixon_config.h" /* generated by config & autoconf */
#endif #endif
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@ -98,7 +108,7 @@ You can see which CLISPEC it generates via clixon_cli -D 2:
#define GENERATE_EXPAND_XMLDB "expand_dbvar" #define GENERATE_EXPAND_XMLDB "expand_dbvar"
/*! Create cligen variable expand entry with xmlkey format string as argument /*! Create cligen variable expand entry with xmlkey format string as argument
* @param[in] h clicon handle * @param[in] h Clixon handle
* @param[in] ys yang_stmt of the node at hand * @param[in] ys yang_stmt of the node at hand
* @param[in] cvtype Type of the cligen variable * @param[in] cvtype Type of the cligen variable
* @param[in] options * @param[in] options
@ -143,7 +153,7 @@ cli_expand_var_generate(clicon_handle h,
} }
/*! Create callback with api_path format string as argument /*! Create callback with api_path format string as argument
* @param[in] h clicon handle * @param[in] h Clixon handle
* @param[in] ys yang_stmt of the node at hand * @param[in] ys yang_stmt of the node at hand
* @param[out] cb The string where the result format string is inserted. * @param[out] cb The string where the result format string is inserted.
* @see cli_dbxml This is where the xmlkeyfmt string is used * @see cli_dbxml This is where the xmlkeyfmt string is used
@ -361,8 +371,7 @@ yang2cli_var_pattern(clicon_handle h,
/* Forward */ /* Forward */
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, genmodel_type gt, static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, genmodel_type gt,
int level, int state, int show_tree, int level, cbuf *cb);
cbuf *cb);
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, char *helptext, cbuf *cb); yang_stmt *ytype, char *helptext, cbuf *cb);
@ -393,7 +402,7 @@ yang2cli_var_sub(clicon_handle h,
cvec *patterns, cvec *patterns,
uint8_t fraction_digits, uint8_t fraction_digits,
cbuf *cb cbuf *cb
) )
{ {
int retval = -1; int retval = -1;
char *type; char *type;
@ -457,7 +466,7 @@ yang2cli_var_sub(clicon_handle h,
if (type && strcmp(type, "identityref") == 0) if (type && strcmp(type, "identityref") == 0)
cprintf(cb, ")"); cprintf(cb, ")");
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
@ -556,7 +565,7 @@ yang2cli_var_union(clicon_handle h,
goto done; goto done;
} }
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
@ -597,7 +606,7 @@ yang2cli_var_leafref(clicon_handle h,
goto done; goto done;
if (completionp){ if (completionp){
if ((ret = cli_expand_var_generate(h, ys, cvtypestr, if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
options, fraction_digits, options, fraction_digits,
cb)) < 0) cb)) < 0)
goto done; goto done;
if (ret == 0) if (ret == 0)
@ -612,10 +621,9 @@ yang2cli_var_leafref(clicon_handle h,
/*! Generate CLI code for Yang leaf statement to CLIgen variable /*! Generate CLI code for Yang leaf statement to CLIgen variable
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] ys Yang statement of original leaf * @param[in] ys Yang statement of original leaf
* @param[in] ys Yang statement of referred node for type (leafref) * @param[in] yreferred Yang statement of referred node for type (leafref)
* @param[in] helptext CLI help text * @param[in] helptext CLI help text
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
* *
* Make a type lookup and complete a cligen variable expression such as <a:string>. * Make a type lookup and complete a cligen variable expression such as <a:string>.
* One complication is yang union, that needs a recursion since it consists of * One complication is yang union, that needs a recursion since it consists of
@ -716,7 +724,7 @@ yang2cli_var(clicon_handle h,
ok: ok:
retval = 0; retval = 0;
done: done:
if (origtype) if (origtype)
free(origtype); free(origtype);
if (patterns) if (patterns)
@ -730,7 +738,6 @@ yang2cli_var(clicon_handle h,
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] callback If set, include a "; cli_set()" callback, otherwise not * @param[in] callback If set, include a "; cli_set()" callback, otherwise not
* @param[in] show_tree Is tree for show cli command
* @param[in] key_leaf Is leaf in a key in a list module * @param[in] key_leaf Is leaf in a key in a list module
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
@ -740,7 +747,6 @@ yang2cli_leaf(clicon_handle h,
genmodel_type gt, genmodel_type gt,
int level, int level,
int callback, int callback,
int show_tree,
int key_leaf, int key_leaf,
cbuf *cb) cbuf *cb)
{ {
@ -768,32 +774,20 @@ yang2cli_leaf(clicon_handle h,
cprintf(cb, "%s", yang_argument_get(ys)); cprintf(cb, "%s", yang_argument_get(ys));
yang2cli_helptext(cb, helptext); yang2cli_helptext(cb, helptext);
cprintf(cb, " "); cprintf(cb, " ");
if (!show_tree || key_leaf) { if (opext && strcmp(opext, "hide") == 0){
if (opext && strcmp(opext, "hide") == 0){ cprintf(cb, ", hide{");
cprintf(cb, ",hide{"); extralevel = 1;
extralevel = 1;
}
if (opext && strcmp(opext, "hide-database-auto-completion") == 0){
cprintf(cb, ",hide-database-auto-completion{");
extralevel = 1;
}
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
goto done;
} }
else{ if (opext && strcmp(opext, "hide-database-auto-completion") == 0){
if (opext && strcmp(opext, "hide") == 0){ cprintf(cb, ", hide-database-auto-completion{");
cprintf(cb, ",hide"); extralevel = 1;
}
if (opext && strcmp(opext, "hide-database-auto-completion") == 0){
cprintf(cb, ",hide-database-auto-completion");
}
} }
if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
goto done;
} }
else{ else{
if (!show_tree || key_leaf) { if (yang2cli_var(h, ys, ys, helptext, cb) < 0)
if (yang2cli_var(h, ys, ys, helptext, cb) < 0) goto done;
goto done;
}
} }
if (callback){ if (callback){
if (cli_callback_generate(h, ys, cb) < 0) if (cli_callback_generate(h, ys, cb) < 0)
@ -803,7 +797,7 @@ yang2cli_leaf(clicon_handle h,
if (extralevel) if (extralevel)
cprintf(cb, "}\n"); cprintf(cb, "}\n");
retval = 0; retval = 0;
done: done:
if (helptext) if (helptext)
free(helptext); free(helptext);
return retval; return retval;
@ -814,8 +808,6 @@ yang2cli_leaf(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[in] show_tree Is tree for show cli command
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -823,8 +815,6 @@ yang2cli_container(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
genmodel_type gt, genmodel_type gt,
int level, int level,
int state,
int show_tree,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -842,7 +832,6 @@ yang2cli_container(clicon_handle h,
goto done; goto done;
/* Hide container "config" if openconfig and OC_COMPRESS */ /* Hide container "config" if openconfig and OC_COMPRESS */
if (strcmp(yang_argument_get(ys), "config") == 0){ if (strcmp(yang_argument_get(ys), "config") == 0){
if (0) fprintf(stderr, "%s config\n", __FUNCTION__);
if (yang_extension_value(ymod, "openconfig-version", "http://openconfig.net/yang/openconfig-ext", &isoc, NULL) < 0) if (yang_extension_value(ymod, "openconfig-version", "http://openconfig.net/yang/openconfig-ext", &isoc, NULL) < 0)
goto done; goto done;
if (isoc && if (isoc &&
@ -875,14 +864,14 @@ yang2cli_container(clicon_handle h,
cprintf(cb, ",hide"); cprintf(cb, ",hide");
} }
if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){ if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){
cprintf(cb, ",hide-database-auto-completion"); cprintf(cb, ", hide-database-auto-completion");
} }
cprintf(cb, ";{\n"); cprintf(cb, ";{\n");
} }
yc = NULL; yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL) while ((yc = yn_each(ys, yc)) != NULL)
if (yang2cli_stmt(h, yc, gt, level+1, state, show_tree, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
goto done; goto done;
if (hide == 0 && hide_oc == 0) if (hide == 0 && hide_oc == 0)
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
@ -898,8 +887,6 @@ yang2cli_container(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[in] show_tree Is tree for show cli command
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -907,8 +894,6 @@ yang2cli_list(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
genmodel_type gt, genmodel_type gt,
int level, int level,
int state,
int show_tree,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -921,7 +906,6 @@ yang2cli_list(clicon_handle h,
char *helptext = NULL; char *helptext = NULL;
char *s; char *s;
int last_key = 0; int last_key = 0;
int extralevel = 0;
char *opext = NULL; char *opext = NULL;
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys)); cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
@ -934,17 +918,15 @@ yang2cli_list(clicon_handle h,
*s = '\0'; *s = '\0';
yang2cli_helptext(cb, helptext); yang2cli_helptext(cb, helptext);
} }
/* Look for autocli-op defined in clixon-lib.yang */ /* Look for autocli-op defined in clixon-lib.yang */
if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0) if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, NULL, &opext) < 0)
goto done; goto done;
if (opext != NULL && strcmp(opext, "hide") == 0){ if (opext != NULL && strcmp(opext, "hide") == 0){
cprintf(cb, ",hide"); cprintf(cb, ",hide");
extralevel = 1;
} }
if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){ if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){
cprintf(cb, ",hide-database-auto-completion"); cprintf(cb, ",hide-database-auto-completion");
extralevel = 1; }
}
/* Loop over all key variables */ /* Loop over all key variables */
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL; cvi = NULL;
@ -961,19 +943,14 @@ yang2cli_list(clicon_handle h,
*/ */
last_key = cvec_next(cvk, cvi)?0:1; last_key = cvec_next(cvk, cvi)?0:1;
if (last_key){ if (last_key){
if (show_tree) { if (cli_callback_generate(h, ys, cb) < 0)
if (cli_callback_generate(h, ys, cb) < 0) goto done;
goto done; cprintf(cb, ";\n");
cprintf(cb, ";\n"); cprintf(cb, "{\n");
cprintf(cb, "{\n");
}
else if (extralevel)
cprintf(cb, "{\n");
} }
if (yang2cli_leaf(h, yleaf, if (yang2cli_leaf(h, yleaf,
(gt==GT_VARS||gt==GT_HIDE||gt==GT_OC_COMPRESS)?GT_NONE:gt, level+1, (gt==GT_VARS||gt==GT_HIDE||gt==GT_OC_COMPRESS)?GT_NONE:gt, level+1,
last_key, show_tree, 1, last_key, 1, cb) < 0)
cb) < 0)
goto done; goto done;
} }
cprintf(cb, "{\n"); cprintf(cb, "{\n");
@ -990,15 +967,14 @@ yang2cli_list(clicon_handle h,
} }
if (cvi != NULL) if (cvi != NULL)
continue; continue;
if (yang2cli_stmt(h, yc, gt, level+1, state, show_tree, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
goto done; goto done;
} }
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
if (last_key && (show_tree||extralevel)) { if (last_key)
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
}
retval = 0; retval = 0;
done: done:
if (helptext) if (helptext)
free(helptext); free(helptext);
return retval; return retval;
@ -1010,8 +986,6 @@ yang2cli_list(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[in] show_tree Is tree for show cli command
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
@example @example
choice interface-type { choice interface-type {
@ -1027,8 +1001,6 @@ yang2cli_choice(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
genmodel_type gt, genmodel_type gt,
int level, int level,
int state,
int show_tree,
cbuf *cb) cbuf *cb)
{ {
int retval = -1; int retval = -1;
@ -1038,7 +1010,7 @@ yang2cli_choice(clicon_handle h,
while ((yc = yn_each(ys, yc)) != NULL) { while ((yc = yn_each(ys, yc)) != NULL) {
switch (yang_keyword_get(yc)){ switch (yang_keyword_get(yc)){
case Y_CASE: case Y_CASE:
if (yang2cli_stmt(h, yc, gt, level+2, state, show_tree, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+2, cb) < 0)
goto done; goto done;
break; break;
case Y_CONTAINER: case Y_CONTAINER:
@ -1046,7 +1018,7 @@ yang2cli_choice(clicon_handle h,
case Y_LEAF_LIST: case Y_LEAF_LIST:
case Y_LIST: case Y_LIST:
default: default:
if (yang2cli_stmt(h, yc, gt, level+1, state, show_tree, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
goto done; goto done;
break; break;
} }
@ -1061,8 +1033,6 @@ yang2cli_choice(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[in] show_tree Is tree for show cli command
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -1070,56 +1040,207 @@ yang2cli_stmt(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
genmodel_type gt, genmodel_type gt,
int level, int level,
int state,
int show_tree,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
int retval = -1; int retval = -1;
if (state || yang_config(ys)){ switch (yang_keyword_get(ys)){
switch (yang_keyword_get(ys)){ case Y_CONTAINER:
case Y_CONTAINER: if (yang2cli_container(h, ys, gt, level, cb) < 0)
if (yang2cli_container(h, ys, gt, level, state, show_tree, cb) < 0) goto done;
break;
case Y_LIST:
if (yang2cli_list(h, ys, gt, level, cb) < 0)
goto done;
break;
case Y_CHOICE:
if (yang2cli_choice(h, ys, gt, level, cb) < 0)
goto done;
break;
case Y_LEAF_LIST:
case Y_LEAF:
if (yang2cli_leaf(h, ys, gt, level, 1, 0, cb) < 0)
goto done;
break;
case Y_CASE:
case Y_SUBMODULE:
case Y_MODULE:
yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL)
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0)
goto done; goto done;
break; break;
case Y_LIST: default: /* skip */
if (yang2cli_list(h, ys, gt, level, state, show_tree, cb) < 0) break;
goto done;
break;
case Y_CHOICE:
if (yang2cli_choice(h, ys, gt, level, state, show_tree, cb) < 0)
goto done;
break;
case Y_LEAF_LIST:
case Y_LEAF:
if (yang2cli_leaf(h, ys, gt, level, 1, show_tree, 0, cb) < 0)
goto done;
break;
case Y_CASE:
case Y_SUBMODULE:
case Y_MODULE:
yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL)
if (yang2cli_stmt(h, yc, gt, level+1, state, show_tree, cb) < 0)
goto done;
break;
default: /* skip */
break;
}
} }
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
/*! Add cv with name to cvec
* @param[in] cvv Either existing or NULL
* @param[in] name Name of cv to add
* @retval cvv Either same as in cvv parameter or new
*/
static cvec*
cvec_add_name(cvec *cvv,
char *name)
{
cg_var *cv= NULL;
if (cvv == NULL &&
(cvv = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
return NULL;
}
if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_add");
return NULL;
}
/* Filter out state data, use "nonconfig" as defined in RFC8040 4.8.1
*/
cv_name_set(cv, name);
return cvv;
}
/*! Recursive post processing of generated cligen parsetree: populate with co_cvec labels
*
* This function adds labels to the generated CLIgen tree using YANG as follows:
* (terminal entry means eg "a ;" where ; is an "empty" child of "a" representing a terminal)
* 1. Add "termfirstkeys" label on terminal entries of LIST keys, except last
* 2. Add "termlist" label on terminal entries of LIST
* 3. Add "termleaf" label on terminal entries of non-empty LEAF/LEAF_LISTs
* 4. Add "leafvar" label on nodes which are children of non-key LEAFs, eg "a <a>" -> "a <a>,leaf"
* 5. Add "nonconfig" label on nodes which have YANG "config false" as children
*
* Then, later, labels can be grouped into specific usages:
* - config: @remove:termfirstkeys,@remote:termlist,@remove:termleaf,@remove:nonconfig,
* - show: @remove:leafvar,@remove:nonconfig
* - showstate: @remove:leafvar
*
* @param[in] h Clixon handle
* @param[in] cop Parent cliegn object (if any)
* @param[in] pt CLIgen parse-tree (generated syntax)
* @param[in] i0 Offset into pt
* @param[in] y YANG node of "pt"
* @param[in] ykey Special case, If y is list, yc can be a leaf key
* @retval 0 OK
* @retval -1 Error
*/
static int
yang2cli_post(clicon_handle h,
cg_obj *cop,
parse_tree *pt,
int i0,
yang_stmt *y,
yang_stmt *ykey)
{
int retval = -1;
cg_obj *co;
int i;
yang_stmt *yc;
int yciskey;
int ycislastkey;
enum rfc_6020 ykeyword;
ykeyword = yang_keyword_get(y);
for (i = i0; i<pt_len_get(pt); i++){
if ((co = pt_vec_i_get(pt, i)) == NULL){
clicon_err(OE_YANG, 0, "Empty object in parsetreelist"); /* shouldnt happen */
goto done;
}
if (co->co_type == CO_EMPTY){
if (ykeyword == Y_LIST){
if (ykey){
/* key, list has a <cr> which is marked as "show" */
ycislastkey = 0;
yang_key_match(y, yang_argument_get(ykey), &ycislastkey);
if (!ycislastkey || (cop && cop->co_type==CO_COMMAND))
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termfirstkeys")) == NULL)
goto done;
}
else{
/* key, list has a <cr> which is marked as "show" */
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termlist")) == NULL)
goto done;
}
}
else if (ykeyword == Y_LEAF || ykeyword == Y_LEAF_LIST){
char *origtype = NULL;
yang_stmt *yrestype = NULL;
if (yang_type_get(y, &origtype, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (origtype && strcmp(origtype,"empty") != 0)
if ((co->co_cvec = cvec_add_name(co->co_cvec, "termleaf")) == NULL)
goto done;
if (origtype)
free(origtype);
}
continue;
}
if ((yc = yang_find_datanode(y, co->co_command)) == NULL)
continue;
yciskey = yang_keyword_get(y) == Y_LIST && yang_key_match(y, co->co_command, NULL);
/* If leaf add "leafvar" label for non-key leafs
* Not a key leaf?
* : y is LIST &
*/
if ((yang_keyword_get(yc) == Y_LEAF) ||
yang_keyword_get(yc) == Y_LEAF_LIST){
/* add empty show
regular should have ; on last
other ; should be marked as ;
Only last key */
if (co->co_type == CO_COMMAND && !co_terminal(co, NULL)){
cg_obj *coe;
if ((coe = co_new(NULL, co)) == NULL) {
goto done;
}
coe->co_type = CO_EMPTY;
coe = co_insert(co_pt_get(co), coe);
}
/* XXX move to next recursion level ? */
int j;
cg_obj *coj;
if (!yciskey)
for (j = 0; j<pt_len_get(co_pt_get(co)); j++){
if ((coj = pt_vec_i_get(co_pt_get(co), j)) == NULL)
continue;
if (coj->co_type == CO_EMPTY)
continue;
if ((coj->co_cvec = cvec_add_name(coj->co_cvec, "leafvar")) == NULL)
goto done;
}
}
/* If state: Add nonconfig label*/
if (!yang_config(yc)){
if ((co->co_cvec = cvec_add_name(co->co_cvec, "nonconfig")) == NULL)
goto done;
}
/* If y is list and yc is key, then call with y */
if (yciskey){
if (yang2cli_post(h, co, co_pt_get(co), 0, y, yc) < 0) // note y not yc
goto done;
}
else if (yang2cli_post(h, co, co_pt_get(co), 0, yc, NULL) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Autocli generator
* Note mix of compile-time runtime
*/
int int
yang2cli_yspec(clicon_handle h, yang2cli_yspec(clicon_handle h,
yang_stmt *yn, yang_stmt *yn,
char *name0, char *name0,
int printgen, int printgen)
int state,
int show_tree)
{ {
int retval = -1; int retval = -1;
parse_tree *pt0 = NULL; parse_tree *pt0 = NULL;
@ -1131,6 +1252,7 @@ yang2cli_yspec(clicon_handle h,
int e; int e;
yang_stmt *ym; yang_stmt *ym;
pt_head *ph; pt_head *ph;
size_t len0;
if ((pt0 = pt_new()) == NULL){ if ((pt0 = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new"); clicon_err(OE_UNIX, errno, "pt_new");
@ -1156,15 +1278,19 @@ yang2cli_yspec(clicon_handle h,
} }
if (e < nexvec) if (e < nexvec)
continue; continue;
if (yang2cli_stmt(h, ym, gt, 0, state, show_tree, cb0) < 0) len0 = cbuf_len(cb0);
if (yang2cli_stmt(h, ym, gt, 0, cb0) < 0)
goto done; goto done;
if (len0 != cbuf_len(cb0))
clicon_debug(1, "%s Generated auto-cli for %s", __FUNCTION__, yang_argument_get(ym));
} }
if (printgen) if (printgen)
clicon_log(LOG_NOTICE, "%s: Top-level api-spec %s:\n%s", clicon_log(LOG_NOTICE, "%s: Top-level cli-spec %s:\n%s",
__FUNCTION__, name0, cbuf_get(cb0)); __FUNCTION__, name0, cbuf_get(cb0));
else else
clicon_debug(2, "%s: Top-level api-spec %s:\n%s", clicon_debug(2, "%s: Top-level cli-spec %s:\n%s",
__FUNCTION__, name0, cbuf_get(cb0)); __FUNCTION__, name0, cbuf_get(cb0));
/* load top-level yangspec cli syntax (that point to modules) */ /* load top-level yangspec cli syntax (that point to modules) */
if (cligen_parse_str(cli_cligen(h), cbuf_get(cb0), "yang2cli", pt0, NULL) < 0) if (cligen_parse_str(cli_cligen(h), cbuf_get(cb0), "yang2cli", pt0, NULL) < 0)
goto done; goto done;
@ -1175,16 +1301,56 @@ yang2cli_yspec(clicon_handle h,
*/ */
if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0) if (cligen_expandv_str2fn(pt0, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
goto done; goto done;
/* Post-processing, iterate over the generated cligen parse-tree with corresponding yang
* Note cannot do it inline in yang2cli above since:
* 1. labels cannot be set on "empty"
* 2. a; <a>, fn() cannot be set properly
*/
ym = NULL;
while ((ym = yn_each(yn, ym)) != NULL){
/* Check if module is in exclude list */
for (e = 0; e < nexvec; e++){
if (strcmp(yang_argument_get(ym), exvec[e]) == 0)
break;
}
if (e < nexvec)
continue;
/* Top level find co from yang:
* XXX: top-level without namespace means check parsetree symbol against each module
* This maye break if there are two top-level symbols with the same name
*/
if (yang2cli_post(h, NULL, pt0, 0, ym, NULL) < 0)
goto done;
}
/* Append cligen tree and name it */ /* Append cligen tree and name it */
if ((ph = cligen_ph_add(cli_cligen(h), name0)) == NULL) if ((ph = cligen_ph_add(cli_cligen(h), name0)) == NULL)
goto done; goto done;
if (cligen_ph_parsetree_set(ph, pt0) < 0) if (cligen_ph_parsetree_set(ph, pt0) < 0)
goto done; goto done;
#if 0
if (printgen){
clicon_log(LOG_NOTICE, "%s: Top-level cli-spec %s", __FUNCTION__, name0);
pt_print1(stderr, pt0, 0);
}
#endif
retval = 0; retval = 0;
done: done:
if (exvec) if (exvec)
free(exvec); free(exvec);
if (cb0) if (cb0)
cbuf_free(cb0); cbuf_free(cb0);
return retval; return retval;
} }
/*! Init yang2cli
*
* Initialize CLIgen generation from YANG models.
* Nothing now
*
* @param[in] h Clixon handle
*/
int
yang2cli_init(clicon_handle h)
{
return 0;
}

View file

@ -52,7 +52,7 @@
/* /*
* Prototypes * Prototypes
*/ */
int yang2cli_yspec(clicon_handle h, yang_stmt *yspec, char *name0, int yang2cli_yspec(clicon_handle h, yang_stmt *yspec, char *name0, int printgen);
int printgen, int state, int show_tree); int yang2cli_init(clicon_handle h);
#endif /* _CLI_GENERATE_H_ */ #endif /* _CLI_GENERATE_H_ */

View file

@ -260,56 +260,59 @@ autocli_start(clicon_handle h,
int printgen) int printgen)
{ {
int retval = -1; int retval = -1;
int autocli_model = 0;
cbuf *show_treename = NULL;
cbuf *treename = NULL;
yang_stmt *yspec; yang_stmt *yspec;
pt_head *ph;
parse_tree *pt = NULL;
/* If autocli disabled quit */ /* Init yang2cli, mainly labels */
if ((autocli_model = clicon_cli_genmodel(h)) == 0) if (yang2cli_init(h) < 0)
goto ok; goto done;
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
/* Load clispec for autocli */ /* Load clispec for autocli */
if (yang_spec_parse_module(h, "clixon-clispec", NULL, yspec)< 0) if (yang_spec_parse_module(h, "clixon-clispec", NULL, yspec)< 0)
goto done; goto done;
/* Get the autocli type, ie HOW the cli is generated (could be much more here) */ /* Get the autocli type, ie HOW the cli is generated (could be much more here) */
/* Create show_treename cbuf */ /* basemodel is labelled tree */
if ((show_treename = cbuf_new()) == NULL){ if (yang2cli_yspec(h, yspec, "basemodel", printgen) < 0)
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Create treename cbuf */
if ((treename = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* The tree name is by default @datamodel but can be changed by option (why would one do that?) */
cprintf(treename, "%s", clicon_cli_model_treename(h));
if (yang2cli_yspec(h, yspec, cbuf_get(treename), printgen, 0, 0) < 0)
goto done; goto done;
/* The tree name is by default @datamodelshow but can be changed by option (why would one do that?) */ /* Create backward compatible tree: @datamodel */
cprintf(show_treename, "%s", clicon_cli_model_treename(h)); if ((ph = cligen_ph_add(cli_cligen(h), "datamodel")) == NULL)
cprintf(show_treename, "show"); goto done;
if (yang2cli_yspec(h, yspec, cbuf_get(show_treename), printgen, 0, 1) < 0) if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
if (cligen_parse_str(cli_cligen(h), "@basemodel, @remove:termfirstkeys, @remove:termlist, @remove:termleaf, @remove:nonconfig;", "datamodel", pt, NULL) < 0)
goto done;
if (cligen_ph_parsetree_set(ph, pt) < 0)
goto done; goto done;
/* Create a tree for config+state. This tree's name has appended "state" to @datamodel /* Create backward compatible tree: @datamodelshow */
*/ if ((ph = cligen_ph_add(cli_cligen(h), "datamodelshow")) == NULL)
if (autocli_model > 1){ goto done;
cprintf(treename, "state"); if ((pt = pt_new()) == NULL){
if (yang2cli_yspec(h, yspec, cbuf_get(treename), printgen, 1, 1) < 0) clicon_err(OE_UNIX, errno, "pt_new");
goto done; goto done;
} }
ok: if (cligen_parse_str(cli_cligen(h), "@basemodel, @remove:leafvar, @remove:nonconfig;", "datamodelshow", pt, NULL) < 0)
goto done;
if (cligen_ph_parsetree_set(ph, pt) < 0)
goto done;
/* Create backward compatible tree: @datamodelstate */
if ((ph = cligen_ph_add(cli_cligen(h), "datamodelstate")) == NULL)
goto done;
if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
if (cligen_parse_str(cli_cligen(h), "@basemodel, @remove:leafvar;", "datamodelstate", pt, NULL) < 0)
goto done;
if (cligen_ph_parsetree_set(ph, pt) < 0)
goto done;
retval = 0; retval = 0;
done: done:
if (show_treename)
cbuf_free(show_treename);
if (treename)
cbuf_free(treename);
return retval; return retval;
} }

View file

@ -469,7 +469,7 @@ api_data_write(clicon_handle h,
*/ */
if ((yp = yang_parent_get(ybot)) != NULL && if ((yp = yang_parent_get(ybot)) != NULL &&
yang_keyword_get(yp) == Y_LIST){ yang_keyword_get(yp) == Y_LIST){
if ((ret = yang_key_match(yp, dname)) < 0) if ((ret = yang_key_match(yp, dname, NULL)) < 0)
goto done; goto done;
if (ret == 1){ /* Match: xdata is a key */ if (ret == 1){ /* Match: xdata is a key */
char *parbod = xml_find_body(xparent, dname); char *parbod = xml_find_body(xparent, dname);

View file

@ -37,9 +37,9 @@ CLICON_PROMPT="%U@%H %W> ";
CLICON_PLUGIN="example_cli"; CLICON_PLUGIN="example_cli";
# Autocli syntax tree operations # Autocli syntax tree operations
edit @datamodel, cli_auto_edit("datamodel"); edit @datamodelshow, cli_auto_edit("basemodel");
up, cli_auto_up("datamodel"); up, cli_auto_up("basemodel");
top, cli_auto_top("datamodel"); top, cli_auto_top("basemodel");
set @datamodel, cli_auto_set(); set @datamodel, cli_auto_set();
merge @datamodel, cli_auto_merge(); merge @datamodel, cli_auto_merge();
create @datamodel, cli_auto_create(); create @datamodel, cli_auto_create();
@ -47,7 +47,6 @@ delete("Delete a configuration item") {
@datamodel, cli_auto_del(); @datamodel, cli_auto_del();
all("Delete whole candidate configuration"), delete_all("candidate"); all("Delete whole candidate configuration"), delete_all("candidate");
} }
validate("Validate changes"), cli_validate(); validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit(); commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit(); quit("Quit"), cli_quit();

View file

@ -60,7 +60,7 @@
enum clicon_err{ enum clicon_err{
/* 0 means error not set) */ /* 0 means error not set) */
OE_DB = 1, /* database registries */ OE_DB = 1, /* database registries */
OE_DAEMON, /* demons: pidfiles, etc */ OE_DAEMON, /* daemons: pidfiles, etc */
OE_EVENTS, /* events, filedescriptors, timeouts */ OE_EVENTS, /* events, filedescriptors, timeouts */
OE_CFG, /* configuration */ OE_CFG, /* configuration */
OE_NETCONF, /* Netconf error */ OE_NETCONF, /* Netconf error */

View file

@ -256,6 +256,7 @@ int yang_print_cb(FILE *f, yang_stmt *yn, clicon_output_cb *fn);
int yang_print(FILE *f, yang_stmt *yn); int yang_print(FILE *f, yang_stmt *yn);
int yang_print_cbuf(cbuf *cb, yang_stmt *yn, int marginal); int yang_print_cbuf(cbuf *cb, yang_stmt *yn, int marginal);
int yang_deviation(yang_stmt *ys, void *arg); int yang_deviation(yang_stmt *ys, void *arg);
int yang_spec_print(FILE *f, yang_stmt *yspec);
int yang_spec_dump(yang_stmt *yspec, int debuglevel); int yang_spec_dump(yang_stmt *yspec, int debuglevel);
int if_feature(yang_stmt *yspec, char *module, char *feature); int if_feature(yang_stmt *yspec, char *module, char *feature);
int ys_populate(yang_stmt *ys, void *arg); int ys_populate(yang_stmt *ys, void *arg);
@ -270,8 +271,7 @@ int yang_config_ancestor(yang_stmt *ys);
int yang_features(clicon_handle h, yang_stmt *yt); int yang_features(clicon_handle h, yang_stmt *yt);
cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi);
int yang_container_cli_hide(yang_stmt *ys, int gt); int yang_container_cli_hide(yang_stmt *ys, int gt);
int yang_key_match(yang_stmt *yn, char *name); int yang_key_match(yang_stmt *yn, char *name, int *lastkey);
int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps); int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps);
int yang_type_cache_get(yang_stmt *ytype, yang_stmt **resolved, int *options, int yang_type_cache_get(yang_stmt *ytype, yang_stmt **resolved, int *options,
cvec **cvv, cvec *patterns, int *rxmode, cvec *regexps, uint8_t *fraction); cvec **cvv, cvec *patterns, int *rxmode, cvec *regexps, uint8_t *fraction);

View file

@ -323,7 +323,7 @@ yang2api_path_fmt_1(yang_stmt *ys,
*/ */
if (yang_keyword_get(ys) == Y_LEAF && yp && if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST && yang_keyword_get(yp) == Y_LIST &&
yang_key_match(yp, ys->ys_argument) == 1) yang_key_match(yp, ys->ys_argument, NULL) == 1)
; ;
else else
#endif #endif
@ -347,7 +347,7 @@ yang2api_path_fmt_1(yang_stmt *ys,
else{ else{
if (yang_keyword_get(ys) == Y_LEAF && yp && if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST){ yang_keyword_get(yp) == Y_LIST){
if (yang_key_match(yp, yang_argument_get(ys)) == 0) if (yang_key_match(yp, yang_argument_get(ys), NULL) == 0)
cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */ cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
} }
else else
@ -477,7 +477,10 @@ api_path_fmt2api_path(const char *api_path_fmt,
if (j == cvec_len(cvv)) /* last element */ if (j == cvec_len(cvv)) /* last element */
; ;
else{ else{
cv = cvec_i(cvv, j++); if ((cv = cvec_i(cvv, j++)) == NULL){
clicon_err(OE_XML, 0, "Number of elements in cvv does not match api_path_fmt string");
goto done;
}
if ((str = cv2str_dup(cv)) == NULL){ if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup"); clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done; goto done;

View file

@ -421,7 +421,7 @@ clixon_process_argv_get(clicon_handle h,
* @param[in] name Process name * @param[in] name Process name
* @param[in] description Description of process * @param[in] description Description of process
* @param[in] netns Namespace netspace (or NULL) * @param[in] netns Namespace netspace (or NULL)
* @param[in] callback * @param[in] callback Wrapper function
* @param[in] argv NULL-terminated vector of vectors * @param[in] argv NULL-terminated vector of vectors
* @param[in] argc Length of argv * @param[in] argc Length of argv
* @retval 0 OK * @retval 0 OK
@ -620,8 +620,8 @@ clixon_process_operation(clicon_handle h,
clicon_debug(1, "%s name:%s op:%s", __FUNCTION__, name, clicon_int2str(proc_operation_map, op0)); clicon_debug(1, "%s name:%s op:%s", __FUNCTION__, name, clicon_int2str(proc_operation_map, op0));
if (_proc_entry_list == NULL) if (_proc_entry_list == NULL)
goto ok; goto ok;
pe = _proc_entry_list; if ((pe = _proc_entry_list) != NULL)
do { do {
if (strcmp(pe->pe_name, name) == 0){ if (strcmp(pe->pe_name, name) == 0){
/* Call wrapper function that eg changes op1 based on config */ /* Call wrapper function that eg changes op1 based on config */
op = op0; op = op0;
@ -983,7 +983,7 @@ clixon_process_waitpid(clicon_handle h)
clicon_debug(1, "%s waitpid(%d) nomatch:%d", __FUNCTION__, pe->pe_pid, wpid); clicon_debug(1, "%s waitpid(%d) nomatch:%d", __FUNCTION__, pe->pe_pid, wpid);
} }
pe = NEXTQ(process_entry_t *, pe); pe = NEXTQ(process_entry_t *, pe);
} while (pe != _proc_entry_list); } while (pe && pe != _proc_entry_list);
retval = 0; retval = 0;
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);

View file

@ -272,7 +272,7 @@ xml2cli_recurse(FILE *f,
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){
xe = NULL; xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){ while ((xe = xml_child_each(x, xe, -1)) != NULL){
if ((match = yang_key_match(ys, xml_name(xe))) < 0) if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done; goto done;
if (!match) if (!match)
continue; continue;
@ -301,7 +301,7 @@ xml2cli_recurse(FILE *f,
xe = NULL; xe = NULL;
while ((xe = xml_child_each(x, xe, -1)) != NULL){ while ((xe = xml_child_each(x, xe, -1)) != NULL){
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){
if ((match = yang_key_match(ys, xml_name(xe))) < 0) if ((match = yang_key_match(ys, xml_name(xe), NULL)) < 0)
goto done; goto done;
if (match){ if (match){
(*fn)(f, "%s\n", cbuf_get(cbpre)); (*fn)(f, "%s\n", cbuf_get(cbpre));
@ -730,7 +730,7 @@ xml_tree_prune_flagged_sub(cxobj *xt,
} }
/* If it is key dont remove it yet (see second round) */ /* If it is key dont remove it yet (see second round) */
if (yt){ if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0) if ((iskey = yang_key_match(yt, xml_name(x), NULL)) < 0)
goto done; goto done;
if (iskey){ if (iskey){
anykey++; anykey++;
@ -758,7 +758,7 @@ xml_tree_prune_flagged_sub(cxobj *xt,
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
/* If it is key remove it here */ /* If it is key remove it here */
if (yt){ if (yt){
if ((iskey = yang_key_match(yt, xml_name(x))) < 0) if ((iskey = yang_key_match(yt, xml_name(x), NULL)) < 0)
goto done; goto done;
if (iskey && xml_purge(x) < 0) if (iskey && xml_purge(x) < 0)
goto done; goto done;
@ -2166,7 +2166,7 @@ xml_copy_marked(cxobj *x0,
* node in list is marked */ * node in list is marked */
if (mark && yt && yang_keyword_get(yt) == Y_LIST){ if (mark && yt && yang_keyword_get(yt) == Y_LIST){
/* XXX: I think yang_key_match is suboptimal here */ /* XXX: I think yang_key_match is suboptimal here */
if ((iskey = yang_key_match(yt, name)) < 0) if ((iskey = yang_key_match(yt, name, NULL)) < 0)
goto done; goto done;
if (iskey){ if (iskey){
if ((xcopy = xml_new(name, x1, CX_ELMNT)) == NULL) if ((xcopy = xml_new(name, x1, CX_ELMNT)) == NULL)

View file

@ -1701,6 +1701,34 @@ yang_print(FILE *f,
return yang_print_cb(f, yn, fprintf); return yang_print_cb(f, yn, fprintf);
} }
/*! Print yang top-level modules only
* @param[in] f File to print to.
* @param[in] yn Yang node to print
* @see yang_print_cbuf
*/
int
yang_spec_print(FILE *f,
yang_stmt *yspec)
{
yang_stmt *ym = NULL;
yang_stmt *yrev;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_YANG, EINVAL, "yspec is not of type YSPEC");
return -1;
}
while ((ym = yn_each(yspec, ym)) != NULL) {
fprintf(f, "%s", yang_key2str(ym->ys_keyword));
fprintf(f, " %s", ym->ys_argument);
if ((yrev = yang_find(ym, Y_REVISION, NULL)) != NULL){
fprintf(f, "@%u", cv_uint32_get(yang_cv_get(yrev)));
}
fprintf(f, ".yang");
fprintf(f, "\n");
}
return 0;
}
/* Log/debug info about top-level (sub)modules no recursion /* Log/debug info about top-level (sub)modules no recursion
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[in] dbglevel Debug level * @param[in] dbglevel Debug level
@ -2005,7 +2033,7 @@ ys_populate_leaf(clicon_handle h,
} }
/* 4. Check if leaf is part of list, if key exists mark leaf as key/unique */ /* 4. Check if leaf is part of list, if key exists mark leaf as key/unique */
if (yparent && yparent->ys_keyword == Y_LIST){ if (yparent && yparent->ys_keyword == Y_LIST){
if ((ret = yang_key_match(yparent, ys->ys_argument)) < 0) if ((ret = yang_key_match(yparent, ys->ys_argument, NULL)) < 0)
goto done; goto done;
} }
yang_cv_set(ys, cv); yang_cv_set(ys, cv);
@ -3417,6 +3445,7 @@ yang_container_cli_hide(yang_stmt *ys,
* The function looks at the LIST argument string (not actual children) * The function looks at the LIST argument string (not actual children)
* @param[in] yn Yang list 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 * @param[in] name Check if this name (eg "b") is a key in the yang key statement
* @param[out] lastkey If 1 this is the last key in a multi-key list
* *
* @retval -1 Error * @retval -1 Error
* @retval 0 No match * @retval 0 No match
@ -3424,11 +3453,13 @@ yang_container_cli_hide(yang_stmt *ys,
*/ */
int int
yang_key_match(yang_stmt *yn, yang_key_match(yang_stmt *yn,
char *name) char *name,
int *lastkey)
{ {
int retval = -1; int retval = -1;
yang_stmt *ys = NULL; yang_stmt *ys = NULL;
int i; int i;
int j;
cvec *cvv = NULL; cvec *cvv = NULL;
cg_var *cv; cg_var *cv;
@ -3437,12 +3468,17 @@ yang_key_match(yang_stmt *yn,
if (ys->ys_keyword == Y_KEY){ if (ys->ys_keyword == Y_KEY){
if ((cvv = yang_arg2cvec(ys, " ")) == NULL) if ((cvv = yang_arg2cvec(ys, " ")) == NULL)
goto done; goto done;
j = 0;
cv = NULL; cv = NULL;
while ((cv = cvec_each(cvv, cv)) != NULL) { while ((cv = cvec_each(cvv, cv)) != NULL) {
j++;
if (strcmp(name, cv_string_get(cv)) == 0){ if (strcmp(name, cv_string_get(cv)) == 0){
if (j == cvec_len(cvv) && lastkey)
*lastkey = 1;
retval = 1; /* match */ retval = 1; /* match */
goto done; goto done;
} }
} }
cvec_free(cvv); cvec_free(cvv);
cvv = NULL; cvv = NULL;

View file

@ -82,4 +82,4 @@ LINKAGE=@LINKAGE@
SH_SUFFIX=@SH_SUFFIX@ SH_SUFFIX=@SH_SUFFIX@
LIBSTATIC_SUFFIX=@LIBSTATIC_SUFFIX@ LIBSTATIC_SUFFIX=@LIBSTATIC_SUFFIX@
CLIXON_YANG_PATCH=@CLIXON_YANG_PATCH@ CLIXON_YANG_PATCH=@CLIXON_YANG_PATCH@
YANG_STANDARD_DIR=@YANG_STANDARD_DIR@

View file

@ -12,6 +12,7 @@ cfg=$dir/conf_yang.xml
fspec=$dir/automode.cli fspec=$dir/automode.cli
fin=$dir/in fin=$dir/in
fstate=$dir/state.xml fstate=$dir/state.xml
fyang=$dir/clixon-example.yang
# Use yang in example # Use yang in example
cat <<EOF > $cfg cat <<EOF > $cfg
@ -20,7 +21,7 @@ cat <<EOF > $cfg
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE> <CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR> <CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR> <CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN> <CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR> <CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE> <CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR> <CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
@ -29,24 +30,47 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE> <CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY> <CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
</clixon-config> </clixon-config>
EOF EOF
cat <<EOF > $fyang
module clixon-example {
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
/* Generic config data */
container table{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
leaf stat{
description "Inline state data for example application";
config false;
type int32;
}
}
}
}
EOF
cat <<EOF > $fspec cat <<EOF > $fspec
CLICON_MODE="example"; CLICON_MODE="example";
CLICON_PROMPT="%U@%H %W> "; CLICON_PROMPT="%U@%H %W> ";
CLICON_PLUGIN="example_cli"; CLICON_PLUGIN="example_cli";
# Autocli syntax tree operations # Autocli syntax tree operations
edit @datamodel, cli_auto_edit("datamodel"); edit @datamodelshow, cli_auto_edit("basemodel");
up, cli_auto_up("datamodel"); up, cli_auto_up("basemodel");
top, cli_auto_top("datamodel"); top, cli_auto_top("basemodel");
set @datamodel, cli_auto_set(); set @datamodel, cli_auto_set();
merge @datamodel, cli_auto_merge(); merge @datamodel, cli_auto_merge();
create @datamodel, cli_auto_create(); create @datamodel, cli_auto_create();
delete("Delete a configuration item") @datamodel, cli_auto_del(); delete("Delete a configuration item") @datamodel, cli_auto_del();
validate("Validate changes"), cli_validate(); validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit(); commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit(); quit("Quit"), cli_quit();
@ -272,6 +296,10 @@ EOF
new "show config netconf" new "show config netconf"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><parameter><name>a</name><value>42</value></parameter></config></edit-config></rpc>]]>]]>" expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "<rpc $DEFAULTNS><edit-config><target><candidate/></target><config><parameter><name>a</name><value>42</value></parameter></config></edit-config></rpc>]]>]]>"
# Negative test
new "config parameter only expect fail"
expectpart "$(echo "set table parameter" | $clixon_cli -f $cfg 2>&1)" 0 "CLI syntax error" "Incomplete command"
if [ $BE -ne 0 ]; then if [ $BE -ne 0 ]; then
new "Kill backend" new "Kill backend"
# Check if premature kill # Check if premature kill

218
test/test_cli_auto_treeref.sh Executable file
View file

@ -0,0 +1,218 @@
#!/usr/bin/env bash
# Tests for @datamodel tree references
# See also test_cli_auto_genmodel.sh
# XXX: cant do "show leaf" only "show leaf <value>"
# 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
# include err() and new() functions and creates $dir
cfg=$dir/conf_yang.xml
fyang=$dir/$APPNAME.yang
fstate=$dir/state.xml
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_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<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>/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
# Four different trees in terms of "config none": none, under container, list, leaf respectively
cat <<EOF > $fyang
module $APPNAME {
namespace "urn:example:clixon";
prefix ex;
container config{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
container statecontainer{
config false;
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
container stateleaf{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
config false;
type string;
}
}
}
container statelist{
list parameter{
config false;
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
}
EOF
# This is state data written to file that backend reads from (on request)
cat <<EOF > $fstate
<statecontainer xmlns="urn:example:clixon">
<parameter>
<name>a</name>
<value>42</value>
</parameter>
</statecontainer>
EOF
cat <<EOF > $clidir/ex.cli
CLICON_MODE="example";
CLICON_PROMPT="%U@%H> ";
show {
base @datamodel, cli_show_auto_state("running", "cli", "set ");
add-nonconfig @datamodelstate, cli_show_auto_state("running", "cli", "set ");
add-show @datamodelshow, cli_show_auto_state("running", "cli", "set ");
}
auto {
edit @datamodelshow, cli_auto_edit("basemodel");
top, cli_auto_top("basemodel");
set @datamodel, cli_auto_set();
show, cli_auto_show("datamodel", "candidate", "text", true, false);
}
EOF
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 -- -sS $fstate"
start_backend -s init -f $cfg -- -sS $fstate
fi
new "wait backend"
wait_backend
#------ base
top=base
new "Show $top"
expectpart "$(echo "show $top ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 config stateleaf statelist --not-- statecontainer '<cr>'
new "Show $top config"
expectpart "$(echo "show $top config ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 parameter '<cr>'
new "Show $top config parameter"
expectpart "$(echo "show $top config parameter ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 parameter '<name>' --not-- '<cr>'
new "Show $top config parameter <name>"
expectpart "$(echo "show $top config parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>' --not-- '<value>'
new "Show $top config parameter <name> value ?"
expectpart "$(echo "show $top config parameter a value ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 '<value>' --not-- '<cr>'
new "Show $top statelist"
expectpart "$(echo "show $top statelist ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 '<cr>' --not-- parameter
new "Show $top stateleaf parameter"
expectpart "$(echo "show $top stateleaf parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 '<cr>' --not-- value
#--------- @add:nonconfig (check state rules)
top=add-nonconfig
new "Show $top"
expectpart "$(echo "show $top ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 config statecontainer stateleaf statelist --not-- '<cr>'
new "Show $top config parameter a"
expectpart "$(echo "show $top config parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>'
new "Show $top statecontainer parameter"
expectpart "$(echo "show $top statecontainer parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>'
new "Show $top statelist parameter"
expectpart "$(echo "show $top statelist parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>'
new "Show $top stateleaf parameter"
expectpart "$(echo "show $top stateleaf parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>'
#--------- @add:show (compare with config: add <cr> at list and leaf)
top=add-show
new "Show $top"
expectpart "$(echo "show $top ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 config stateleaf statelist --not-- statecontainer '<cr>'
new "Show $top config"
expectpart "$(echo "show $top config ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 parameter '<cr>'
# <cr> is enabled on lists
new "Show $top config parameter"
expectpart "$(echo "show $top config parameter ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 parameter '<name>' '<cr>'
new "Show $top config parameter <name>"
expectpart "$(echo "show $top config parameter a ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 value '<cr>' --not-- '<value>'
# Have not succeeded with this, and I am not sure it is necessary?
# Ie to do "show leaf", but not "show leaf <value>"
# <cr> is enabled but no value on leafs
new "Show $top config parameter <name> value"
expectpart "$(echo "show $top config parameter a value ?" | $clixon_cli -f $cfg 2> /dev/null)" 0 '<cr>' --not-- '<value>'
#--------- @add:show+@add:nonconfig
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
rm -rf $dir
new "endtest"
endtest

View file

@ -673,7 +673,7 @@ module clixon-config {
<CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE> <CLICON_CLI_AUTOCLI_EXCLUDE>clixon-restconf</CLICON_CLI_AUTOCLI_EXCLUDE>
means generate autocli for all models except clixon-restconf.yang means generate autocli for all models except clixon-restconf.yang
The value can be a list of space separated module names"; The value can be a list of space separated module names";
default "clixon-restconf"; default "clixon-restconf ietf-yang-library";
} }
leaf CLICON_CLI_VARONLY { leaf CLICON_CLI_VARONLY {
type int32; type int32;